Merge "selenium: invoke jobs to enforce eventual consistency"
[lhc/web/wiklou.git] / resources / src / mediawiki.widgets / MediaSearch / mw.widgets.MediaResourceProvider.js
1 /*!
2 * MediaWiki Widgets - MediaResourceProvider class.
3 *
4 * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
5 * @license The MIT License (MIT); see LICENSE.txt
6 */
7 ( function () {
8
9 /**
10 * MediaWiki media resource provider.
11 *
12 * @class
13 * @extends mw.widgets.APIResultsProvider
14 *
15 * @constructor
16 * @param {string} apiurl The API url
17 * @param {Object} [config] Configuration options
18 * @cfg {string} [scriptDirUrl] The url of the API script
19 */
20 mw.widgets.MediaResourceProvider = function MwWidgetsMediaResourceProvider( apiurl, config ) {
21 config = config || {};
22
23 // Parent constructor
24 mw.widgets.MediaResourceProvider.super.call( this, apiurl, config );
25
26 // Fetching configuration
27 this.scriptDirUrl = config.scriptDirUrl;
28 this.isLocal = config.local !== undefined;
29
30 if ( this.isLocal ) {
31 this.setAPIurl( mw.util.wikiScript( 'api' ) );
32 } else {
33 // If 'apiurl' is set, use that. Otherwise, build the url
34 // from scriptDirUrl and /api.php suffix
35 this.setAPIurl( this.getAPIurl() || ( this.scriptDirUrl + '/api.php' ) );
36 }
37
38 this.siteInfoPromise = null;
39 this.thumbSizes = [];
40 this.imageSizes = [];
41 };
42
43 /* Inheritance */
44 OO.inheritClass( mw.widgets.MediaResourceProvider, mw.widgets.APIResultsProvider );
45
46 /* Methods */
47
48 /**
49 * @inheritdoc
50 */
51 mw.widgets.MediaResourceProvider.prototype.getStaticParams = function () {
52 return $.extend(
53 {},
54 // Parent method
55 mw.widgets.MediaResourceProvider.super.prototype.getStaticParams.call( this ),
56 {
57 action: 'query',
58 iiprop: 'dimensions|url|mediatype|extmetadata|timestamp|user',
59 iiextmetadatalanguage: this.getLang(),
60 prop: 'imageinfo'
61 }
62 );
63 };
64
65 /**
66 * Initialize the source and get the site info.
67 *
68 * Connect to the api url and retrieve the siteinfo parameters
69 * that are required for fetching results.
70 *
71 * @return {jQuery.Promise} Promise that resolves when the class
72 * properties are set.
73 */
74 mw.widgets.MediaResourceProvider.prototype.loadSiteInfo = function () {
75 var provider = this;
76
77 if ( !this.siteInfoPromise ) {
78 this.siteInfoPromise = new mw.Api().get( {
79 action: 'query',
80 meta: 'siteinfo'
81 } )
82 .then( function ( data ) {
83 provider.setImageSizes( data.query.general.imagelimits || [] );
84 provider.setThumbSizes( data.query.general.thumblimits || [] );
85 provider.setUserParams( {
86 // Standard width per resource
87 iiurlwidth: provider.getStandardWidth()
88 } );
89 } );
90 }
91 return this.siteInfoPromise;
92 };
93
94 /**
95 * Override parent method and get results from the source
96 *
97 * @param {number} [howMany] The number of items to pull from the API
98 * @return {jQuery.Promise} Promise that is resolved into an array
99 * of available results, or is rejected if no results are available.
100 */
101 mw.widgets.MediaResourceProvider.prototype.getResults = function ( howMany ) {
102 var xhr,
103 aborted = false,
104 provider = this;
105
106 return this.loadSiteInfo()
107 .then( function () {
108 if ( aborted ) {
109 return $.Deferred().reject();
110 }
111 xhr = provider.fetchAPIresults( howMany );
112 return xhr;
113 } )
114 .then(
115 function ( results ) {
116 if ( !results || results.length === 0 ) {
117 provider.toggleDepleted( true );
118 return [];
119 }
120 return results;
121 },
122 // Process failed, return an empty promise
123 function () {
124 provider.toggleDepleted( true );
125 return $.Deferred().resolve( [] );
126 }
127 )
128 .promise( { abort: function () {
129 aborted = true;
130 if ( xhr ) {
131 xhr.abort();
132 }
133 } } );
134 };
135
136 /**
137 * Get continuation API data
138 *
139 * @param {number} howMany The number of results to retrieve
140 * @return {Object} API request data
141 */
142 mw.widgets.MediaResourceProvider.prototype.getContinueData = function () {
143 return {};
144 };
145
146 /**
147 * Set continuation data for the next page
148 *
149 * @param {Object} continueData Continuation data
150 */
151 mw.widgets.MediaResourceProvider.prototype.setContinue = function () {
152 };
153
154 /**
155 * Sort the results
156 *
157 * @param {Object[]} results API results
158 * @return {Object[]} Sorted results
159 */
160 mw.widgets.MediaResourceProvider.prototype.sort = function ( results ) {
161 return results;
162 };
163
164 /**
165 * Call the API for search results.
166 *
167 * @param {number} howMany The number of results to retrieve
168 * @return {jQuery.Promise} Promise that resolves with an array of objects that contain
169 * the fetched data.
170 */
171 mw.widgets.MediaResourceProvider.prototype.fetchAPIresults = function ( howMany ) {
172 var xhr, api,
173 provider = this;
174
175 if ( !this.isValid() ) {
176 return $.Deferred().reject().promise( { abort: $.noop } );
177 }
178
179 api = this.isLocal ? new mw.Api() : new mw.ForeignApi( this.getAPIurl(), { anonymous: true } );
180 xhr = api.get( $.extend( {}, this.getStaticParams(), this.getUserParams(), this.getContinueData( howMany ) ) );
181 return xhr
182 .then( function ( data ) {
183 var page, newObj, raw,
184 results = [];
185
186 if ( data.error ) {
187 provider.toggleDepleted( true );
188 return [];
189 }
190
191 if ( data.continue ) {
192 // Update the offset for next time
193 provider.setContinue( data.continue );
194 } else {
195 // This is the last available set of results. Mark as depleted!
196 provider.toggleDepleted( true );
197 }
198
199 // If the source returned no results, it will not have a
200 // query property
201 if ( data.query ) {
202 raw = data.query.pages;
203 if ( raw ) {
204 // Strip away the page ids
205 for ( page in raw ) {
206 if ( !raw[ page ].imageinfo ) {
207 // The search may give us pages that belong to the File:
208 // namespace but have no files in them, either because
209 // they were deleted or imported wrongly, or just started
210 // as pages. In that case, the response will not include
211 // imageinfo. Skip those files.
212 continue;
213 }
214 newObj = raw[ page ].imageinfo[ 0 ];
215 newObj.title = raw[ page ].title;
216 newObj.index = raw[ page ].index;
217 results.push( newObj );
218 }
219 }
220 }
221 return provider.sort( results );
222 } )
223 .promise( { abort: xhr.abort } );
224 };
225
226 /**
227 * Set name
228 *
229 * @param {string} name
230 */
231 mw.widgets.MediaResourceProvider.prototype.setName = function ( name ) {
232 this.name = name;
233 };
234
235 /**
236 * Get name
237 *
238 * @return {string} name
239 */
240 mw.widgets.MediaResourceProvider.prototype.getName = function () {
241 return this.name;
242 };
243
244 /**
245 * Get standard width, based on the provider source's thumb sizes.
246 *
247 * @return {number|undefined} fetchWidth
248 */
249 mw.widgets.MediaResourceProvider.prototype.getStandardWidth = function () {
250 return ( this.thumbSizes && this.thumbSizes[ this.thumbSizes.length - 1 ] ) ||
251 ( this.imageSizes && this.imageSizes[ 0 ] ) ||
252 // Fall back on a number
253 300;
254 };
255
256 /**
257 * Get prop
258 *
259 * @return {string} prop
260 */
261 mw.widgets.MediaResourceProvider.prototype.getFetchProp = function () {
262 return this.fetchProp;
263 };
264
265 /**
266 * Set prop
267 *
268 * @param {string} prop
269 */
270 mw.widgets.MediaResourceProvider.prototype.setFetchProp = function ( prop ) {
271 this.fetchProp = prop;
272 };
273
274 /**
275 * Set thumb sizes
276 *
277 * @param {number[]} sizes Available thumbnail sizes
278 */
279 mw.widgets.MediaResourceProvider.prototype.setThumbSizes = function ( sizes ) {
280 this.thumbSizes = sizes;
281 };
282
283 /**
284 * Set image sizes
285 *
286 * @param {number[]} sizes Available image sizes
287 */
288 mw.widgets.MediaResourceProvider.prototype.setImageSizes = function ( sizes ) {
289 this.imageSizes = sizes;
290 };
291
292 /**
293 * Get thumb sizes
294 *
295 * @return {number[]} sizes Available thumbnail sizes
296 */
297 mw.widgets.MediaResourceProvider.prototype.getThumbSizes = function () {
298 return this.thumbSizes;
299 };
300
301 /**
302 * Get image sizes
303 *
304 * @return {number[]} sizes Available image sizes
305 */
306 mw.widgets.MediaResourceProvider.prototype.getImageSizes = function () {
307 return this.imageSizes;
308 };
309
310 /**
311 * Check if this source is valid.
312 *
313 * @return {boolean} Source is valid
314 */
315 mw.widgets.MediaResourceProvider.prototype.isValid = function () {
316 return this.isLocal ||
317 // If we don't have either 'apiurl' or 'scriptDirUrl'
318 // the source is invalid, and we will skip it
319 this.apiurl !== undefined ||
320 this.scriptDirUrl !== undefined;
321 };
322 }() );