2014-12-16 01:06:05 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor MediaWiki Initialization ApiResponseCache class.
|
|
|
|
*
|
2023-12-01 16:06:11 +00:00
|
|
|
* @copyright See AUTHORS.txt
|
2014-12-16 01:06:05 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
2015-04-30 16:03:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* MediaWiki API batch queue.
|
|
|
|
*
|
|
|
|
* Used to queue up lists of items centrally to get information about in batches
|
|
|
|
* of requests.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends OO.EventEmitter
|
|
|
|
* @constructor
|
2018-05-05 14:13:15 +00:00
|
|
|
* @param {mw.Api} [api] API object to use. Defaults to new mw.Api()
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
2018-05-05 14:13:15 +00:00
|
|
|
ve.init.mw.ApiResponseCache = function VeInitMwApiResponseCache( api ) {
|
2015-04-30 16:03:30 +00:00
|
|
|
// Mixin constructor
|
|
|
|
OO.EventEmitter.call( this );
|
|
|
|
|
2018-05-05 14:13:15 +00:00
|
|
|
this.api = api || new mw.Api();
|
|
|
|
|
2015-04-30 16:03:30 +00:00
|
|
|
// Keys are titles, values are deferreds
|
|
|
|
this.deferreds = {};
|
|
|
|
|
|
|
|
// Keys are page names, values are link data objects
|
|
|
|
// This is kept for synchronous retrieval of cached values via #getCached
|
|
|
|
this.cacheValues = {};
|
|
|
|
|
|
|
|
// Array of page titles queued to be looked up
|
|
|
|
this.queue = [];
|
|
|
|
|
|
|
|
this.schedule = ve.debounce( this.processQueue.bind( this ), 0 );
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
OO.mixinClass( ve.init.mw.ApiResponseCache, OO.EventEmitter );
|
|
|
|
|
|
|
|
/* Static methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process each page in the response of an API request
|
|
|
|
*
|
|
|
|
* @abstract
|
|
|
|
* @static
|
2021-06-04 11:58:18 +00:00
|
|
|
* @param {Object} page
|
2015-04-30 16:03:30 +00:00
|
|
|
* @return {Object|undefined} Any relevant info that we want to cache and return.
|
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.static.processPage = null;
|
|
|
|
|
|
|
|
/**
|
2021-06-04 12:00:21 +00:00
|
|
|
* @param {string} title
|
|
|
|
* @return {string}
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.static.normalizeTitle = function ( title ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const titleObj = mw.Title.newFromText( title );
|
2015-04-30 16:03:30 +00:00
|
|
|
if ( !titleObj ) {
|
|
|
|
return title;
|
|
|
|
}
|
|
|
|
return titleObj.getPrefixedText();
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up data about a title. If the data about this title is already in the cache, this
|
|
|
|
* returns an already-resolved promise. Otherwise, it returns a pending promise and schedules
|
|
|
|
* an request to retrieve the data.
|
2015-08-19 18:21:01 +00:00
|
|
|
*
|
2021-06-04 11:58:18 +00:00
|
|
|
* @param {string} title
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {jQuery.Promise} Promise that will be resolved with the data once it's available
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.prototype.get = function ( title ) {
|
|
|
|
if ( typeof title !== 'string' ) {
|
|
|
|
// Don't bother letting things like undefined or null make it all the way through,
|
|
|
|
// just reject them here. Otherwise they'll cause problems or exceptions at random
|
|
|
|
// other points in this file.
|
2019-11-02 05:06:28 +00:00
|
|
|
return ve.createDeferred().reject().promise();
|
2015-04-30 16:03:30 +00:00
|
|
|
}
|
|
|
|
title = this.constructor.static.normalizeTitle( title );
|
|
|
|
if ( !Object.prototype.hasOwnProperty.call( this.deferreds, title ) ) {
|
2019-11-02 05:06:28 +00:00
|
|
|
this.deferreds[ title ] = ve.createDeferred();
|
2015-04-30 16:03:30 +00:00
|
|
|
this.queue.push( title );
|
|
|
|
this.schedule();
|
|
|
|
}
|
2015-08-19 17:33:02 +00:00
|
|
|
return this.deferreds[ title ].promise();
|
2015-04-30 16:03:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up data about a page in the cache. If the data about this page is already in the cache,
|
|
|
|
* this returns that data. Otherwise, it returns undefined.
|
|
|
|
*
|
|
|
|
* @param {string} name Normalized page title
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {Object|undefined} Cache data for this name.
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.prototype.getCached = function ( name ) {
|
|
|
|
if ( Object.prototype.hasOwnProperty.call( this.cacheValues, name ) ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
return this.cacheValues[ name ];
|
2015-04-30 16:03:30 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when a new entry is added to the cache.
|
|
|
|
*
|
2024-04-29 11:52:40 +00:00
|
|
|
* @event ve.init.mw.ApiResponseCache#add
|
2015-04-30 16:03:30 +00:00
|
|
|
* @param {Object} entries Cache entries that were added. Object mapping names to data objects.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2015-07-28 22:02:32 +00:00
|
|
|
* Add entries to the cache. Does not overwrite already-set entries.
|
2015-04-30 16:03:30 +00:00
|
|
|
*
|
|
|
|
* @param {Object} entries Object keyed by page title, with the values being data objects
|
2024-04-29 18:14:26 +00:00
|
|
|
* @fires ve.init.mw.ApiResponseCache#add
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.prototype.set = function ( entries ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
for ( const name in entries ) {
|
2015-07-28 22:02:32 +00:00
|
|
|
if ( !Object.prototype.hasOwnProperty.call( this.deferreds, name ) ) {
|
2019-11-02 05:06:28 +00:00
|
|
|
this.deferreds[ name ] = ve.createDeferred();
|
2015-07-28 22:02:32 +00:00
|
|
|
}
|
2015-08-19 17:33:02 +00:00
|
|
|
if ( this.deferreds[ name ].state() === 'pending' ) {
|
|
|
|
this.deferreds[ name ].resolve( entries[ name ] );
|
|
|
|
this.cacheValues[ name ] = entries[ name ];
|
2015-07-28 22:02:32 +00:00
|
|
|
}
|
2015-04-30 16:03:30 +00:00
|
|
|
}
|
|
|
|
this.emit( 'add', Object.keys( entries ) );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an API request promise to deal with a list of titles
|
|
|
|
*
|
|
|
|
* @abstract
|
|
|
|
* @param subqueue
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.prototype.getRequestPromise = null;
|
2014-12-16 01:06:05 +00:00
|
|
|
|
2015-04-30 16:03:30 +00:00
|
|
|
/**
|
|
|
|
* Perform any scheduled API requests.
|
|
|
|
*
|
|
|
|
* @private
|
2024-04-29 18:14:26 +00:00
|
|
|
* @fires ve.init.mw.ApiResponseCache#add
|
2015-04-30 16:03:30 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ApiResponseCache.prototype.processQueue = function () {
|
2024-05-21 14:22:56 +00:00
|
|
|
const rejectSubqueue = ( rejectQueue ) => {
|
|
|
|
for ( let i = 0, len = rejectQueue.length; i < len; i++ ) {
|
2024-05-03 16:49:51 +00:00
|
|
|
this.deferreds[ rejectQueue[ i ] ].reject();
|
2015-04-30 16:03:30 +00:00
|
|
|
}
|
2024-05-03 16:49:51 +00:00
|
|
|
};
|
2015-04-30 16:03:30 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
const processResult = ( data ) => {
|
2024-05-21 16:40:36 +00:00
|
|
|
const pages = ( data.query && data.query.pages ) || data.pages,
|
2015-04-30 16:03:30 +00:00
|
|
|
processed = {};
|
|
|
|
|
2024-05-29 16:03:23 +00:00
|
|
|
const mappedTitles = [];
|
2024-04-30 16:44:25 +00:00
|
|
|
[ 'redirects', 'normalized', 'converted' ].forEach( ( map ) => {
|
2024-05-29 16:03:23 +00:00
|
|
|
ve.batchPush( mappedTitles, ( data.query && data.query[ map ] ) || [] );
|
2018-06-13 19:15:37 +00:00
|
|
|
} );
|
|
|
|
|
2015-04-30 16:03:30 +00:00
|
|
|
if ( pages ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
let page, processedPage;
|
|
|
|
for ( const pageid in pages ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
page = pages[ pageid ];
|
2024-05-03 16:49:51 +00:00
|
|
|
processedPage = this.constructor.static.processPage( page );
|
2015-04-30 16:03:30 +00:00
|
|
|
if ( processedPage !== undefined ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
processed[ page.title ] = processedPage;
|
2014-12-16 01:06:05 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-21 14:22:56 +00:00
|
|
|
for ( let i = 0; i < mappedTitles.length; i++ ) {
|
2018-06-13 19:15:37 +00:00
|
|
|
// Locate the title in mapped titles, if any.
|
|
|
|
if ( mappedTitles[ i ].to === page.title ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const from = mappedTitles[ i ].fromencoded === '' ?
|
2018-06-13 19:15:37 +00:00
|
|
|
decodeURIComponent( mappedTitles[ i ].from ) :
|
|
|
|
mappedTitles[ i ].from;
|
|
|
|
processed[ from ] = processedPage;
|
2018-05-23 10:48:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-05-03 16:49:51 +00:00
|
|
|
this.set( processed );
|
2014-12-16 01:06:05 +00:00
|
|
|
}
|
2024-05-03 16:49:51 +00:00
|
|
|
};
|
2015-04-30 16:03:30 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
const queue = this.queue;
|
2015-04-30 16:03:30 +00:00
|
|
|
this.queue = [];
|
|
|
|
while ( queue.length ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const subqueue = queue.splice( 0, 50 ).map( this.constructor.static.normalizeTitle );
|
2015-04-30 16:03:30 +00:00
|
|
|
this.getRequestPromise( subqueue )
|
|
|
|
.then( processResult )
|
|
|
|
|
|
|
|
// Reject everything in subqueue; this will only reject the ones
|
|
|
|
// that weren't already resolved above, because .reject() on an
|
|
|
|
// already resolved Deferred is a no-op.
|
|
|
|
.then( rejectSubqueue.bind( null, subqueue ) );
|
|
|
|
}
|
|
|
|
};
|