mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-11-28 10:00:18 +00:00
Merge "Add TaskQueue class"
This commit is contained in:
commit
400ddd7b53
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
|
138
resources/mmv/model/mmv.model.TaskQueue.js
Normal file
138
resources/mmv/model/mmv.model.TaskQueue.js
Normal 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 ) );
|
308
tests/qunit/mmv/model/mmv.model.TaskQueue.test.js
Normal file
308
tests/qunit/mmv/model/mmv.model.TaskQueue.test.js
Normal 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 ) );
|
Loading…
Reference in a new issue