mediawiki-extensions-Visual.../modules/ve-mw/dm/models/ve.dm.MWMediaResourceProvider.js
Moriel Schottlender aa9eb95455 Refactor MWMediaSearchWidget to use a queue and providers
Change the media search widget to work with resource queues and
providers. Create providers based on the user's filerepo settings
and aggregate their responses with the media queue. Stop asking
for more results from providers that are depleted.

Also fixes a rather nasty infinite-loop bug where the API returns
only very few images, and the UI keeps asking for more.

Bug: T78161
Bug: T88764
Change-Id: I65aed3446cd1f056476c56e6e04522c70e49e595
2015-02-06 16:45:56 -08:00

510 lines
11 KiB
JavaScript

/*!
* VisualEditor DataModel MWMediaResourceProvider class.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki media resource provider.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.dm.MWMediaResourceProvider = function VeDmMWMediaResourceProvider( config ) {
config = config || {};
// Source Configuration
this.apiurl = this.setAPIurl( config.apiurl );
this.name = config.name;
this.displayName = config.displayName;
this.local = config.local;
this.scriptDirUrl = config.scriptDirUrl;
// ajaxOptions configuration
this.dataType = config.dataType || 'jsonp';
this.cached = config.cached || true;
// Fetching configuration
this.fetchLimit = config.limit || 30;
this.iiprop = config.iiprop || [ 'dimensions', 'url', 'mediatype', 'extmetadata', 'timestamp' ];
this.fetchProp = config.fetchProp || 'imageinfo';
this.lang = config.lang || 'en';
this.siteInfoPromise = null;
this.thumbSizes = [];
this.imageSizes = [];
this.depleted = false;
this.offset = config.offset || 0;
this.setQuery( config.query || '' );
// Mixin constructors
OO.EventEmitter.call( this );
};
/* Setup */
OO.initClass( ve.dm.MWMediaResourceProvider );
OO.mixinClass( ve.dm.MWMediaResourceProvider, OO.EventEmitter );
/* Methods */
/**
* Initialize the source and get the site info.
*
* Connect to the api url and retrieve the siteinfo parameters
* that are required for fetching results.
*
* @return {jQuery.Promise} Promise that resolves when the class
* properties are set.
*/
ve.dm.MWMediaResourceProvider.prototype.loadSiteInfo = function () {
var provider = this;
if ( !this.siteInfoPromise ) {
this.siteInfoPromise = ve.init.target.constructor.static.apiRequest( {
action: 'query',
meta: 'siteinfo'
} )
.then( function ( data ) {
if ( data.error ) {
return $.Deferred().reject();
}
provider.setImageSizes( ve.getProp( data, 'query', 'general', 'imagelimits' ) || [] );
provider.setThumbSizes( ve.getProp( data, 'query', 'general', 'thumblimits' ) || [] );
} );
}
return this.siteInfoPromise;
};
/**
* Get results from the source
*
* @return {jQuery.Promise} Promise that is resolved into an array
* of available results, or is rejected if no results are available.
*/
ve.dm.MWMediaResourceProvider.prototype.getResults = function ( howMany ) {
var xhr,
aborted = false,
provider = this;
return this.loadSiteInfo()
.then( function () {
if ( aborted ) {
return $.Deferred().reject();
}
xhr = provider.fetchAPIresults( howMany );
return xhr;
} )
.then(
function ( results ) {
if ( results.length === 0 ) {
provider.toggleDepleted( true );
}
return results;
},
// Process failed, return an empty promise
function () {
provider.toggleDepleted( true );
return $.Deferred().resolve( [] );
}
)
.promise( { abort: function () {
aborted = true;
if ( xhr ) {
xhr.abort();
}
} } );
};
/**
* Call the API for search results.
*
* @param {number} howMany The number of results to retrieve
* @return {jQuery.Promise} Promise that resolves with an array of objects that contain
* the fetched data.
*/
ve.dm.MWMediaResourceProvider.prototype.fetchAPIresults = function ( howMany ) {
var xhr,
ajaxOptions = {},
query = this.getQuery(),
provider = this,
apiCallConfig = {
action: 'query',
generator: 'search',
gsrsearch: query,
gsrnamespace: 6,
continue: '',
gsroffset: this.getOffset(),
prop: this.getFetchProp(),
// Language of the extmetadata details
iiextmetadatalanguage: this.getLang(),
iiprop: this.getIiProp().join( '|' ),
iiurlheight: this.getMaxHeight(),
// Standard width per resource
iiurlwidth: this.getStandardWidth()
};
howMany = howMany || 20;
// Initial number of images
apiCallConfig.gsrlimit = howMany;
if ( this.isValid() ) {
if ( this.isLocal() ) {
ajaxOptions = {
url: mw.util.wikiScript( 'api' ),
// If the url is local use json
dataType: 'json'
};
} else {
ajaxOptions = {
// If 'apiurl' is set, use that. Otherwise, build the url
// from scriptDirUrl and /api.php suffix
url: this.apiurl || ( this.scriptDirUrl + '/api.php' ),
// If the url is not the same origin use jsonp
dataType: 'jsonp',
// JSON-P requests are not cached by default and get a &_=random trail.
// While setting cache=true will still bypass cache in most case due to the
// callback parameter, at least drop the &_=random trail which triggers
// an API warning (invalid parameter).
cache: true
};
}
xhr = ve.init.target.constructor.static.apiRequest( apiCallConfig, ajaxOptions );
return xhr
.then( function ( data ) {
var page, newObj,
results = [],
raw = ve.getProp( data, 'query', 'pages' );
if ( data[ 'continue' ] ) {
// Update the offset for next time
provider.setOffset( data[ 'continue' ].gsroffset );
} else {
// This is the last available set of result. Mark as depleted!
provider.toggleDepleted( true );
}
if ( raw ) {
// Strip away the page ids
for ( page in raw ) {
newObj = raw[page].imageinfo[0];
newObj.title = raw[page].title;
results.push( newObj );
}
}
return results;
} )
.promise( { abort: xhr.abort } );
}
};
/**
* Get search query
*
* @return {string} search query
*/
ve.dm.MWMediaResourceProvider.prototype.getQuery = function () {
return this.query;
};
/**
* Set search query
*
* @param {string} value
*/
ve.dm.MWMediaResourceProvider.prototype.setQuery = function ( value ) {
if ( this.query !== value ) {
this.query = value;
// Reset offset
this.setOffset( 0 );
// Reset depleted status
this.toggleDepleted( false );
}
};
/**
* Set api url
*
* @param {string} API url
*/
ve.dm.MWMediaResourceProvider.prototype.setAPIurl = function ( url ) {
this.apiurl = url;
};
/**
* Set api url
*
* @return {string} API url
*/
ve.dm.MWMediaResourceProvider.prototype.getAPIurl = function () {
return this.apiurl;
};
/**
* Set name
*
* @param {string} name
*/
ve.dm.MWMediaResourceProvider.prototype.setName = function ( name ) {
this.name = name;
};
/**
* Get name
*
* @returns {string} name
*/
ve.dm.MWMediaResourceProvider.prototype.getName = function () {
return this.name;
};
/**
* Get displayName
*
* @return {string} displayName
*/
ve.dm.MWMediaResourceProvider.prototype.getDisplayName = function () {
return this.displayName;
};
/**
* Set displayName
*
* @param {string} displayName
*/
ve.dm.MWMediaResourceProvider.prototype.setDisplayName = function ( displayName ) {
this.displayName = displayName;
};
/**
* Get isLocal value
*
* @return {boolean} isLocal value
*/
ve.dm.MWMediaResourceProvider.prototype.isLocal = function () {
return this.local;
};
/**
* Get ScriptDirUrl
*
* @return {string} ScriptDirUrl
*/
ve.dm.MWMediaResourceProvider.prototype.getScriptDirUrl = function () {
return this.scriptDirUrl;
};
/**
* Set scriptDirUrl
*
* @param {string} scriptDirUrl
*/
ve.dm.MWMediaResourceProvider.prototype.setScriptDirUrl = function ( scriptDirUrl ) {
this.scriptDirUrl = scriptDirUrl;
};
/**
* Get dataType
*
* @return {string} dataType
*/
ve.dm.MWMediaResourceProvider.prototype.getDataType = function () {
return this.dataType;
};
/**
* Set dataType
*
* @param {string} dataType
*/
ve.dm.MWMediaResourceProvider.prototype.setDataType = function ( dataType ) {
this.dataType = dataType;
};
/**
* Get cached
*
* @return {boolean} cached
*/
ve.dm.MWMediaResourceProvider.prototype.isCached = function () {
return this.cached;
};
/**
* Get fetch limit or 'page' size. This is the number
* of results per request.
*
* @return {number} limit
*/
ve.dm.MWMediaResourceProvider.prototype.getFetchLimit = function () {
return this.limit;
};
/**
* Set limit
*
* @param {number} limit
*/
ve.dm.MWMediaResourceProvider.prototype.setFetchLimit = function ( limit ) {
this.limit = limit;
};
/**
* Get properties
*
* @return {string[]} properties
*/
ve.dm.MWMediaResourceProvider.prototype.getIiProp = function () {
return this.iiprop;
};
/**
* Get max height
*
* @return {number|undefined} Maximum height
*/
ve.dm.MWMediaResourceProvider.prototype.getMaxHeight = function () {
return this.maxHeight;
};
/**
* Set maximum height
*
* @param {number} Maximum height
*/
ve.dm.MWMediaResourceProvider.prototype.setMaxHeight = function ( maxHeight ) {
this.maxHeight = maxHeight;
};
/**
* Get standard width, based on the provider source's thumb sizes.
*
* @return {number|undefined} fetchWidth
*/
ve.dm.MWMediaResourceProvider.prototype.getStandardWidth = function () {
return this.thumbSizes && this.thumbSizes[ this.thumbSizes.length - 1 ];
};
/**
* Get prop
*
* @return {string} prop
*/
ve.dm.MWMediaResourceProvider.prototype.getFetchProp = function () {
return this.fetchProp;
};
/**
* Set prop
*
* @param {string} prop
*/
ve.dm.MWMediaResourceProvider.prototype.setFetchProp = function ( prop ) {
this.fetchProp = prop;
};
/**
* Get lang
*
* @return {string} lang
*/
ve.dm.MWMediaResourceProvider.prototype.getLang = function () {
return this.lang;
};
/**
* Set lang
*
* @param {string} lang
*/
ve.dm.MWMediaResourceProvider.prototype.setLang = function ( lang ) {
this.lang = lang;
};
/**
* Get Offset
*
* @return {number} Offset
*/
ve.dm.MWMediaResourceProvider.prototype.getOffset = function () {
return this.offset;
};
/**
* Set Offset
*
* @param {number} Offset
*/
ve.dm.MWMediaResourceProvider.prototype.setOffset = function ( offset ) {
this.offset = offset;
};
/**
* Set thumb sizes
*
* @param {number[]} sizes Available thumbnail sizes
*/
ve.dm.MWMediaResourceProvider.prototype.setThumbSizes = function ( sizes ) {
this.thumbSizes = sizes;
};
/**
* Set image sizes
*
* @param {number[]} sizes Available image sizes
*/
ve.dm.MWMediaResourceProvider.prototype.setImageSizes = function ( sizes ) {
this.imageSizes = sizes;
};
/**
* Get thumb sizes
*
* @returns {number[]} sizes Available thumbnail sizes
*/
ve.dm.MWMediaResourceProvider.prototype.getThumbSizes = function () {
return this.thumbSizes;
};
/**
* Get image sizes
*
* @returns {number[]} sizes Available image sizes
*/
ve.dm.MWMediaResourceProvider.prototype.getImageSizes = function () {
return this.imageSizes;
};
/**
* Check whether the provider is depleted
*
* @return {boolean} depleted
*/
ve.dm.MWMediaResourceProvider.prototype.isDepleted = function () {
return this.depleted;
};
/**
* Toggle depleted state
*
* @param {boolean} depleted
*/
ve.dm.MWMediaResourceProvider.prototype.toggleDepleted = function ( isDepleted ) {
this.depleted = isDepleted !== undefined ? isDepleted : !this.depleted;
};
/**
* Check if this source is valid and ready for search.
* @return {boolean} Source is valid
*/
ve.dm.MWMediaResourceProvider.prototype.isValid = function () {
return this.getQuery() &&
(
// If we don't have either 'apiurl' or 'scriptDirUrl'
// the source is invalid, and we will skip it
this.apiurl || this.scriptDirUrl !== undefined
);
};