update-service.js 10.1 KB
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2016 Adobe Systems Incorporated
 * All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */
/* globals Promise */
define('screens/player/firmware/update/impl/update-service', [
    'screens/player/firmware/update/update',
    'screens/player/firmware/preferences/preferences',
    'screens/player/firmware/update/spi/update',
    'screens/player/shared/serviceadmin',
    'screens/player/store/store',
    'screens/player/store/statemachine-adapter',
    'module'
], function(Update, Preferences, UpdateSpi, ServiceAdmin, Store, StateMachineAdapter, mod) {
    'use strict';

    /**
     * Default options for the component.
     *
     * @type {Object}
     *
     * @property {String} [pkgName='firmware'] Name of the firmware package
     * @property {String} storePath path of the state in the store
     * @property {String} serverURL URL for the content sync server
     */
    var DEFAULTS = {
        pkgName: 'firmware',
        storePath: 'progress',
        serverURL: null
    };

    function ok(instance) {
        return function() {
            instance._sm.transition('ok');
        };
    }

    function error(instance) {
        return function() {
            instance._sm.transition('error');
        };
    }

    /**
     * Defines the Update Service
     *
     * @class UpdateImpl
     * @implements {Update}
     */
    function UpdateImpl() {
        this.options = _.defaults({}, DEFAULTS);
    }

    UpdateImpl.prototype = _.assign({}, Update, {

        serviceId: mod.id,

        serviceRanking: 10,

        /**
         * @memberof UpdateImpl
         * @private
         */
        _serverUrl: '',

        /**
         * @memberof UpdateImpl
         * @inheritdoc
         */
        activate: function() {
            var self = this;

            function _onPreferencesSubscribe(state) {
                self._serverUrl = state.server;
            }

            return Promise.all([
                new Promise(function(resolve, reject) {
                    ServiceAdmin.onServiceHighestRankedStart(UpdateSpi.serviceName, function(spi) {
                        self._updateSpi = spi;
                        resolve();
                    });
                }),
                new Promise(function(resolve, reject) {
                    ServiceAdmin.onServiceHighestRankedStart(Store.serviceName, function(store) {
                        self._preferencesChangeListener = store.subscribe(_onPreferencesSubscribe, Preferences.NAMESPACE);
                        resolve();
                    });
                })
            ]).then(function() {
                return self._initStateMachine();
            });
        },

        /**
         * @memberof UpdateImpl
         * @inheritdoc
         */
        deactivate: function() {
            this._sm.destroy().then(function() {
                if (this._preferencesChangeListener) {
                    var store = ServiceAdmin.getService(Store.serviceName);
                    store.unsubscribe(this._preferencesChangeListener);
                    this._preferencesChangeListener = null;
                }
            }.bind(this));
        },

        /**
         * @memberof UpdateImpl
         * @private
         *
         * @returns {Object} the update specs
         */
        _getUpdateSpec: function() {
            return {
                serverURL: this._serverUrl,
                id: this.options.pkgName,
                idPrefix: '',
                // Indicate that self-signed certificates should be trusted
                // should be set to `false` in production.
                trustAllHosts: true,
                requestHeaders: {
                    'X-Requested-With': 'XMLHttpRequest' // this is to avoid the 404 error page
                }
            };
        },

        /**
         * initialises the state machine
         * @memberof UpdateImpl
         * @private
         * @returns {Promise} a promise that the state machine was initialized
         */
        _initStateMachine: function() {
            var self = this;
            var states = {};
            states[Update.STATES.START] = {
                '@enter': function() {
                    self._updateSpi.start(self._getUpdateSpec()).then(
                        ok(self),
                        error(self)
                    );
                },
                'ok': Update.STATES.INITIALIZED,
                'error': 'error'
            };
            states[Update.STATES.INITIALIZED] = {
                'check': Update.STATES.CHECKING,
                'next': Update.STATES.CHECKING
            };
            states[Update.STATES.ERROR] = {
                'next': Update.STATES.INITIALIZED
            };
            states[Update.STATES.CHECKING] = {
                '@enter': function() {
                    self._updateSpi.checkForUpdates(self._getUpdateSpec()).then(
                        function(state) {
                            self._sm.transition(state);
                        },
                        error(self)
                    );
                },
                'error': Update.STATES.ERROR,
                'ok-new': Update.STATES.HAS_UPDATES,
                'ok': Update.STATES.UP_TO_DATE
            };
            states[Update.STATES.HAS_UPDATES] = {
                'check': Update.STATES.CHECKING,
                'update': Update.STATES.UPDATING,
                'next': Update.STATES.UPDATING
            };
            states[Update.STATES.UP_TO_DATE] = {
                'check': Update.STATES.CHECKING,
                'next': Update.STATES.CHECKING
            };
            states[Update.STATES.UPDATING] = {
                '@enter': function() {
                    self._updateSpi.download(self._getUpdateSpec()).then(
                        ok(self),
                        error(self)
                    );
                },
                'error': Update.STATES.ERROR,
                'ok': Update.STATES.UPDATED
            };
            states[Update.STATES.UPDATED] = {
                'next': Update.STATES.INSTALLING
            };
            states[Update.STATES.INSTALLING] = {
                '@enter': function() {
                    self._updateSpi.install().then(
                        ok(self),
                        error(self)
                    );
                },
                'error': Update.STATES.ERROR,
                'ok': Update.STATES.INITIALIZED
            };

            if (this._sm) {
                this._sm.destroy();
            }
            this._sm = (new StateMachineAdapter(
                Update.NAMESPACE + '.' + this.options.storePath,
                Update.STATES.START,
                {
                    states: states
                }
            ));

            return this._sm.initialize();
        },

        /**
         * Resets the current running update task
         * @memberof UpdateImpl
         * @private
         */
        _resetUpdateTask: function() {
            this._updateTask = null;
        },

        /**
         * @memberof UpdateImpl
         * @inheritdoc
         */
        hasStepByStepSupport: function() {
            return this._updateSpi.hasStepByStepSupport();
        },

        /**
         * @memberof UpdateImpl
         * @inheritdoc
         */
        update: function() {
            var self = this;
            var store = ServiceAdmin.getService(Store.serviceName);

            if (this._updateTask) {
                return Promise.reject('Update task still running.');
            }

            // we check for updates again
            this._sm.transition('check');


            this._updateTask = new Promise(function(resolve, reject) {
                var updateChangeListener;
                var handler = function(state) {
                    if (state.progress === Update.STATES.INITIALIZED) {
                        // we first need to look for an update
                        self._sm.transition('next');
                    } else if (state.progress === Update.STATES.HAS_UPDATES) {
                        self._sm.transition('update');
                    } else if (state.progress === Update.STATES.UP_TO_DATE) {
                        store.unsubscribe(updateChangeListener);
                        reject('There is no update available to install.');
                    } else if (state.progress === Update.STATES.UPDATED) {
                        self._sm.transition('next');
                    } else if (state.progress === Update.STATES.INSTALLING) {
                        store.unsubscribe(updateChangeListener);
                        resolve();
                        self._sm.transition('ok');
                    } else if (state.progress === Update.STATES.ERROR) {
                        store.unsubscribe(updateChangeListener);
                        reject('Update failed.');
                    }
                };

                updateChangeListener = store.subscribe(handler, Update.NAMESPACE);
                store.requestState();
            });

            this._updateTask.then(
                this._resetUpdateTask.bind(this),
                this._resetUpdateTask.bind(this)
            );

            return this._updateTask;
        },

        /**
         * @memberof UpdateImpl
         * @inheritdoc
         */
        nextStep: function() {
            this._sm.transition('next');
        }

    });

    return UpdateImpl;
});

/* istanbul ignore next */
require([
    'screens/player/firmware/update/impl/update-service',
    'screens/player/shared/serviceadmin'
], function(Service, ServiceAdmin) {
    'use strict';
    ServiceAdmin.register(new Service());
});