Merge "Add TaskQueue class"

This commit is contained in:
jenkins-bot 2014-02-13 09:35:37 +00:00 committed by Gerrit Code Review
commit 400ddd7b53
5 changed files with 464 additions and 0 deletions

View file

@ -184,6 +184,16 @@ call_user_func( function() {
),
), $moduleInfo( 'mmv/model' ) );
$wgResourceModules['mmv.model.TaskQueue'] = array_merge( array(
'scripts' => array(
'mmv.model.TaskQueue.js',
),
'dependencies' => array(
'mmv.model',
),
), $moduleInfo( 'mmv/model' ) );
$wgResourceModules['mmv.provider'] = array_merge( array(
'scripts' => array(
'mmv.provider.Api.js',
@ -199,6 +209,7 @@ call_user_func( function() {
'dependencies' => array(
'mediawiki.Title',
'mmv.model',
'mmv.model.TaskQueue',
'oojs',
),
), $moduleInfo( 'mmv/provider' ) );
@ -379,6 +390,7 @@ call_user_func( function() {
'mmv.model.Image',
'mmv.model.Repo',
'mmv.model.Thumbnail',
'mmv.model.TaskQueue',
'mmv.provider',
'mediawiki.language',
'mmv.multilightbox',

View file

@ -123,6 +123,7 @@ class MultimediaViewerHooks {
'tests/qunit/mmv.testhelpers.js',
'tests/qunit/mmv.test.js',
'tests/qunit/mmv.model.test.js',
'tests/qunit/mmv/model/mmv.model.TaskQueue.test.js',
'tests/qunit/mmv.ThumbnailWidthCalculator.test.js',
'tests/qunit/provider/mmv.provider.Api.test.js',
'tests/qunit/mmv.performance.test.js',

View file

@ -61,6 +61,11 @@
* A jQuery promise object.
*/
/**
* @class jQuery.Deferred
* A jQuery deferred object.
*/
/**
* @class jQuery.Event
* An event object with extra jQuery data.

View file

@ -0,0 +1,138 @@
/*
* This file is part of the MediaWiki extension MultimediaViewer.
*
* MultimediaViewer 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 2 of the License, or
* (at your option) any later version.
*
* MultimediaViewer 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 MultimediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( mw, $ ) {
var tqp;
/**
* @class mw.mmv.model.TaskQueue
*
* A queue which holds a list of tasks (functions). The tasks will be executed in order,
* each starting when the previous one has finished (or failed).
*
* @constructor
*/
function TaskQueue() {
/**
* The list of functions to execute.
* @protected
* @property {Array.<function()>}
*/
this.queue = [];
/**
* State of the task queue (running, finished etc)
* @protected
* @type {mw.mmv.model.TaskQueue.State}
*/
this.state = TaskQueue.State.NOT_STARTED;
/**
* A deferred which shows the state of the queue.
* @protected
* @type {jQuery.Deferred}
*/
this.deferred = $.Deferred();
}
tqp = TaskQueue.prototype;
/**
* Adds a task. The task should be a function which returns a promise. (Other return values are
* permitted, and will be taken to mean that the task has finished already.) The next task will
* start when the promise resolves (or rejects).
*
* Tasks can only be added before the queue is first executed.
* @param {function()} task
*/
tqp.push = function( task ) {
if ( this.state !== TaskQueue.State.NOT_STARTED ) {
throw 'Task queue already started!';
}
this.queue.push( task );
};
/**
* Execute the queue. The tasks will be performed in order. No more tasks can be added to the
* queue.
* @return {jQuery.Promise} a promise which will resolve when the queue execution is finished,
* or reject when it is cancelled.
*/
tqp.execute = function() {
if ( this.state === TaskQueue.State.NOT_STARTED ) {
this.state = TaskQueue.State.RUNNING;
this.runNextTask( 0, $.Deferred().resolve() );
}
return this.deferred;
};
/**
* Runs the next task once the current one has finished.
* @param {number} index
* @param {jQuery.Promise} currentTask
*/
tqp.runNextTask = function( index, currentTask ) {
var taskQueue = this;
function handleThen() {
if ( !taskQueue.queue[index] ) {
taskQueue.state = TaskQueue.State.FINISHED;
taskQueue.queue = []; // just to be sure there are no memory leaks
taskQueue.deferred.resolve();
return;
}
taskQueue.runNextTask( index + 1, $.when( taskQueue.queue[index]() ) );
}
if ( this.state !== TaskQueue.State.RUNNING ) {
return;
}
currentTask.then( handleThen, handleThen );
};
/**
* Cancel the queue. No more tasks will be executed.
*/
tqp.cancel = function() {
this.state = TaskQueue.State.CANCELLED;
this.queue = []; // just to be sure there are no memory leaks
this.deferred.reject();
};
/**
* State of the task queue (running, finished etc)
* @enum {string} mw.mmv.model.TaskQueue.State
*/
TaskQueue.State = {
/** not executed yet, tasks can still be added */
NOT_STARTED: 'not_started',
/** some task is being executed */
RUNNING: 'running',
/** all tasks finished, queue can be discarded */
FINISHED: 'finished',
/** cancel() function has been called, queue can be discarded */
CANCELLED: 'cancelled'
};
mw.mmv.model.TaskQueue = TaskQueue;
}( mediaWiki, jQuery ) );

View file

@ -0,0 +1,308 @@
/*
* This file is part of the MediaWiki extension MediaViewer.
*
* MediaViewer 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 2 of the License, or
* (at your option) any later version.
*
* MediaViewer 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 MediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function( mw, $ ) {
QUnit.module( 'mmv.model.TaskQueue', QUnit.newMwEnvironment() );
QUnit.test( 'TaskQueue constructor sanity check', 1, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
assert.ok( taskQueue, 'TaskQueue created successfully' );
} );
QUnit.test( 'Queue length check', 2, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
assert.strictEqual( taskQueue.queue.length, 0, 'queue is initially empty' );
taskQueue.push( function() {} );
assert.strictEqual( taskQueue.queue.length, 1, 'queue length is incremented on push' );
} );
QUnit.test( 'State check', 3, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
task = $.Deferred();
taskQueue.push( function() { return task; } );
assert.strictEqual( taskQueue.state, mw.mmv.model.TaskQueue.State.NOT_STARTED,
'state is initially NOT_STARTED' );
taskQueue.execute().then( function() {
assert.strictEqual( taskQueue.state, mw.mmv.model.TaskQueue.State.FINISHED,
'state is FINISHED after execution finished' );
} );
assert.strictEqual( taskQueue.state, mw.mmv.model.TaskQueue.State.RUNNING,
'state is RUNNING after execution started' );
task.resolve();
} );
QUnit.test( 'State check for cancellation', 1, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
task = $.Deferred();
taskQueue.push( function() { return task; } );
taskQueue.execute();
taskQueue.cancel();
assert.strictEqual( taskQueue.state, mw.mmv.model.TaskQueue.State.CANCELLED,
'state is CANCELLED after cancellation' );
} );
QUnit.test( 'Test executing empty queue', 1, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
QUnit.stop();
taskQueue.execute().done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Simple execution test', 2, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
QUnit.stop();
taskQueue.push( function() {
assert.ok( true, 'Task executed successfully' );
QUnit.start();
} );
QUnit.stop();
taskQueue.execute().done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Task execution order test', 4, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
nextExpectedTask = 1;
QUnit.stop();
taskQueue.push( function() {
assert.strictEqual( nextExpectedTask, 1, 'First task executed in order' );
nextExpectedTask++;
QUnit.start();
} );
QUnit.stop();
taskQueue.push( function() {
var deferred = $.Deferred();
assert.strictEqual( nextExpectedTask, 2, 'Second task executed in order' );
nextExpectedTask++;
setTimeout( function() {
deferred.resolve();
QUnit.start();
}, 0 );
return deferred;
} );
QUnit.stop();
taskQueue.push( function() {
assert.strictEqual( nextExpectedTask, 3, 'Second task executed in order' );
QUnit.start();
} );
QUnit.stop();
taskQueue.execute().done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Double execution test', 2, function( assert ) {
var taskExecuted = false,
taskQueue = new mw.mmv.model.TaskQueue();
QUnit.stop();
taskQueue.push( function() {
assert.ok( !taskExecuted, 'Task executed only once' );
taskExecuted = true;
QUnit.start();
} );
QUnit.stop();
taskQueue.execute().then( function() {
return taskQueue.execute();
} ).done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Parallel execution test', 2, function( assert ) {
var taskExecuted = false,
taskQueue = new mw.mmv.model.TaskQueue();
QUnit.stop();
taskQueue.push( function() {
assert.ok( !taskExecuted, 'Task executed only once' );
taskExecuted = true;
QUnit.start();
} );
QUnit.stop();
$.when(
taskQueue.execute(),
taskQueue.execute()
).done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Test push after execute', 1, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
taskQueue.execute();
try {
taskQueue.push( function() {} );
} catch (e) {
assert.ok( e, 'Exception thrown when trying to push to an already running queue' );
}
} );
QUnit.test( 'Test failed task', 1, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue();
taskQueue.push( function() {
return $.Deferred().reject();
} );
QUnit.stop();
taskQueue.execute().done( function() {
assert.ok( true, 'Queue promise resolved' );
QUnit.start();
} );
} );
QUnit.test( 'Test that tasks wait for each other', 1, function( assert ) {
var longRunningTaskFinished = false,
taskQueue = new mw.mmv.model.TaskQueue();
taskQueue.push( function() {
var deferred = $.Deferred();
setTimeout( function() {
longRunningTaskFinished = true;
deferred.resolve();
}, 0 );
return deferred;
} );
QUnit.stop();
taskQueue.push( function() {
assert.ok( longRunningTaskFinished, 'Task waits for previous task to finish' );
QUnit.start();
} );
taskQueue.execute();
} );
QUnit.test( 'Test cancellation before start', 2, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
taskTriggeredByHand = false,
verificationTask = function() {
assert.ok( taskTriggeredByHand, 'Task was not triggered' );
QUnit.start();
};
taskQueue.push( verificationTask );
taskQueue.cancel();
QUnit.stop();
taskQueue.execute().fail( function() {
assert.ok( true, 'Queue promise rejected' );
QUnit.start();
} );
QUnit.stop();
setTimeout( function() {
taskTriggeredByHand = true;
verificationTask();
}, 0 );
} );
QUnit.test( 'Test cancellation within callback', 2, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
taskTriggeredByHand = false,
verificationTask = function() {
assert.ok( taskTriggeredByHand, 'Task was not triggered' );
QUnit.start();
};
taskQueue.push( function() {
taskQueue.cancel();
} );
taskQueue.push( verificationTask );
QUnit.stop();
taskQueue.execute().fail( function() {
assert.ok( true, 'Queue promise rejected' );
QUnit.start();
} );
QUnit.stop();
setTimeout( function() {
taskTriggeredByHand = true;
verificationTask();
}, 0 );
} );
QUnit.test( 'Test cancellation from task', 2, function( assert ) {
var taskQueue = new mw.mmv.model.TaskQueue(),
taskTriggeredByHand = false,
task1 = $.Deferred(),
verificationTask = function() {
assert.ok( taskTriggeredByHand, 'Task was not triggered' );
QUnit.start();
};
taskQueue.push( function() {
return task1;
} );
taskQueue.push( verificationTask );
QUnit.stop();
taskQueue.execute().fail( function() {
assert.ok( true, 'Queue promise rejected' );
QUnit.start();
} );
setTimeout( function() {
taskQueue.cancel();
task1.resolve();
}, 0 );
QUnit.stop();
setTimeout( function() {
taskTriggeredByHand = true;
verificationTask();
}, 0 );
} );
}( mediaWiki, jQuery ) );