/* Authors: * Petr Vobornik * * Copyright (C) 2012 Red Hat * see file 'COPYING' for use and warranty information * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ define([ 'dojo/_base/lang', 'dojo/_base/array', 'dojo/_base/declare', 'dojo/Deferred', 'dojo/promise/all', 'dojo/topic', '../ordered-map' ], function(lang, array, declare, Deferred, all, topic, ordered_map) { /** * Phase * * This class does not exist, it's only for documentation purposes. * * @class _base.Phase_controller.phase * @abstract */ /** * Name * @property {string} name */ /** * Tasks * @property {Array.<_base.Phase_controller.task>} tasks */ /** * Phase task * * This class does not exist, it's only for documentation purposes. * * @class _base.Phase_controller.task * @abstract */ /** * Name * @property {number} priority */ /** * Tasks * @property {Function} handler */ /** * Phase Controller * * Creates synchronization points - phases - in application life cycle. * * Phases: * * - are ordered * - consist of task * - phase finishes when all task finishes * - new phases can be added at runtime * * @class _base.Phase_controller */ var Phase_controller = declare(null, { /** * Phases * @property {ordered_map.} */ phases: null, /** * Current phase name * @property {string} */ current: null, /** * Run all phases in a row starting from current, or the first when * current is not set. */ run: function() { if (this.current !== null) { var phase = this.phases.get(this.current); this._run_phase(phase); } else { this.next_phase(true); } }, /** * Runs phase * * 1. Sorts tasks of the phase based on their priority. * 2. Runs all task sequentially. * 3. Waits for all tasks to complete (in case of asynchronous ones) * 4. Optionally runs next phase * * @param {_base.Phase_controller.phase} phase Phase to run * @param {boolean} next_phase Whether to run next phase when current finishes */ _run_phase: function(phase, next_phase) { if (!phase) return; this.current = phase.name; topic.publish('phase-start', { phase: phase.name }); var promises = []; var tasks = phase.tasks.sort(function(a,b) { return a.priority-b.priority; }); array.forEach(tasks, function(task) { var promise; try { promise = task.handler(); } catch (e) { var fail = new Deferred(); fail.reject(e, true); promise = fail.promise; } promises.push(promise); }); all(promises).then(lang.hitch(this, function(results) { topic.publish('phase-finished', { phase: phase.name, results: results }); if (next_phase) { this.next_phase(next_phase); } }), function(results) { topic.publish('phase-error', { phase: phase.name, results: results }); // don't go for next phase on error, let app decide what to do }); }, /** * Selects next phase and then runs it. * * @param {boolean} continuous Whether to run phases continuously */ next_phase: function(continuous) { var phase; if (this.current === null) { phase = this.phases.get_value_by_index(0); } else { var index = this.phases.get_key_index(this.current); phase = this.phases.get_value_by_index(index + 1); } this._run_phase(phase, continuous); }, /** * Adds task for a phase. * * At phase execution, tasks are sorted by priority and executed in * that order. * * @param {string} phase_name Name of associated phase * @param {Function} handler Task handler. Should return promise if async. * @param {number} [priority=10] Priority of task. */ add_task: function(phase_name, handler, priority) { var phase = this.phases.get(phase_name); if (!phase) { window.console.warn('no such phase: ' + phase_name); return; } if (typeof priority !== 'number') priority = 10; phase.tasks.push({ priority: priority, handler: handler }); }, /** * Adds a phase * * Possible options: * * - before: 'name-of-phase' * - after: 'name-of-phase' * - position: 'position for new phase' * * @param {string} phase name * @param {Array.<_base.Phase_controller.task>} tasks * @param {Object} options */ add_phase: function(name, tasks, options) { var phase = { name: name, tasks: tasks || [] }; var position; if (options) { if (options.before) { position = this.phases.get_key_index(options.before); } else if (options.after) { position = this.phases.get_key_index(options.after); if (position === -1) position = this.phases.length; else position++; } else if (options.position) { position = options.position; } } this.phases.put(name, phase, position); }, /** * Checks if phases with given name exists * * @param {string} name * @return {boolean} */ exists: function(name) { return !!this.phases.get(name); }, constructor: function(spec) { spec = spec || {}; this.phases = ordered_map(); var phases = spec.phases || []; array.forEach(phases, lang.hitch(this, function(phase) { this.add_phase(phase); })); } }); return Phase_controller; });