OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * MetadataCache is a map from url to an object containing properties. | 6 * MetadataCache is a map from url to an object containing properties. |
7 * Properties are divided by types, and all properties of one type are accessed | 7 * Properties are divided by types, and all properties of one type are accessed |
8 * at once. | 8 * at once. |
9 * Some of the properties: | 9 * Some of the properties: |
10 * { | 10 * { |
11 * filesystem: size, modificationTime, icon | 11 * filesystem: size, modificationTime |
12 * internal: presence | 12 * internal: presence |
13 * gdata: pinned, present, hosted, editUrl, contentUrl, availableOffline | 13 * gdata: pinned, present, hosted, editUrl, contentUrl, availableOffline |
| 14 * streaming: url |
| 15 * |
| 16 * Following are not fetched for non-present gdata files. |
| 17 * media: artist, album, title, width, height, imageTransform, etc. |
14 * thumbnail: url, transform | 18 * thumbnail: url, transform |
15 * media: artist, album, title | 19 * |
| 20 * Following are always fetched from content, and so force the downloading |
| 21 * of remote gdata files. One should use this for required content metadata, |
| 22 * i.e. image orientation. |
| 23 * fetchedMedia: width, height, etc. |
16 * } | 24 * } |
17 * | 25 * |
18 * Typical usages: | 26 * Typical usages: |
19 * { | 27 * { |
20 * cache.get([entry1, entry2], 'gdata', function(gdata) { | 28 * cache.get([entry1, entry2], 'gdata|filesystem', function(metadata) { |
21 * if (gdata[0].pinned && gdata[1].pinned) alert("They are both pinned!"); | 29 * if (metadata[0].gdata.pinned && metadata[1].filesystem.size == 0) |
| 30 * alert("Pinned and empty!"); |
22 * }); | 31 * }); |
23 * | 32 * |
24 * cache.set(entry, 'internal', {presence: 'deleted'}); | 33 * cache.set(entry, 'internal', {presence: 'deleted'}); |
25 * | 34 * |
26 * cache.clear([fileUrl1, fileUrl2], 'filesystem'); | 35 * cache.clear([fileUrl1, fileUrl2], 'filesystem'); |
27 * | 36 * |
28 * // Getting fresh value. | 37 * // Getting fresh value. |
29 * cache.clear(entry, 'thumbnail'); | 38 * cache.clear(entry, 'thumbnail'); |
30 * cache.get(entry, 'thumbnail', function(thumbnail) { | 39 * cache.get(entry, 'thumbnail', function(thumbnail) { |
31 * img.src = thumbnail.url; | 40 * img.src = thumbnail.url; |
32 * }); | 41 * }); |
| 42 * |
| 43 * var cached = cache.getCached(entry, 'filesystem'); |
| 44 * var size = (cached && cached.size) || UNKNOWN_SIZE; |
33 * } | 45 * } |
34 * | 46 * |
35 * TODO(dgozman): eviction. | |
36 * @constructor | 47 * @constructor |
37 */ | 48 */ |
38 function MetadataCache() { | 49 function MetadataCache() { |
39 /** | 50 /** |
40 * Map from urls to entries. Entry contains |properties| - an hierarchical | 51 * Map from urls to entries. Entry contains |properties| - an hierarchical |
41 * object of values, and an object for each metadata provider: | 52 * object of values, and an object for each metadata provider: |
42 * <prodiver-id>: { time, callbacks } | 53 * <prodiver-id>: { time, callbacks } |
43 * @private | 54 * @private |
44 */ | 55 */ |
45 this.cache_ = {}; | 56 this.cache_ = {}; |
46 | 57 |
47 /** | 58 /** |
48 * List of metadata providers. | 59 * List of metadata providers. |
49 * @private | 60 * @private |
50 */ | 61 */ |
51 this.providers_ = []; | 62 this.providers_ = []; |
52 | 63 |
53 /** | 64 /** |
54 * List of observers added. Each one is an object with fields: | 65 * List of observers added. Each one is an object with fields: |
55 * re - regexp of urls; | 66 * re - regexp of urls; |
56 * type - metadata type; | 67 * type - metadata type; |
57 * callback - the callback. | 68 * callback - the callback. |
58 * TODO(dgozman): pass entries to observer if present. | 69 * TODO(dgozman): pass entries to observer if present. |
59 * @private | 70 * @private |
60 */ | 71 */ |
61 this.observers_ = []; | 72 this.observers_ = []; |
62 this.observerId_ = 0; | 73 this.observerId_ = 0; |
63 | 74 |
64 this.batchCount_ = 0; | 75 this.batchCount_ = 0; |
| 76 this.totalCount_ = 0; |
| 77 |
| 78 /** |
| 79 * Time of first get query of the current batch. Items updated later than this |
| 80 * will not be evicted. |
| 81 * @private |
| 82 */ |
| 83 this.lastBatchStart_ = new Date(); |
65 } | 84 } |
66 | 85 |
67 /** | 86 /** |
68 * Observer type: it will be notified if the url changed is exactlt the same | 87 * Observer type: it will be notified if the url changed is exactlt the same |
69 * as the url passed. | 88 * as the url passed. |
70 */ | 89 */ |
71 MetadataCache.EXACT = 0; | 90 MetadataCache.EXACT = 0; |
72 | 91 |
73 /** | 92 /** |
74 * Observer type: it will be notified if the url changed is an immediate child | 93 * Observer type: it will be notified if the url changed is an immediate child |
75 * of the url passed. | 94 * of the url passed. |
76 */ | 95 */ |
77 MetadataCache.CHILDREN = 1; | 96 MetadataCache.CHILDREN = 1; |
78 | 97 |
79 /** | 98 /** |
80 * Observer type: it will be notified if the url changed is any descendant | 99 * Observer type: it will be notified if the url changed is any descendant |
81 * of the url passed. | 100 * of the url passed. |
82 */ | 101 */ |
83 MetadataCache.DESCENDANTS = 2; | 102 MetadataCache.DESCENDANTS = 2; |
84 | 103 |
85 /** | 104 /** |
| 105 * Minimum number of items in cache to start eviction. |
| 106 */ |
| 107 MetadataCache.EVICTION_NUMBER = 1000; |
| 108 |
| 109 /** |
86 * @return {MetadataCache!} The cache with all providers. | 110 * @return {MetadataCache!} The cache with all providers. |
87 */ | 111 */ |
88 MetadataCache.createFull = function() { | 112 MetadataCache.createFull = function() { |
89 var cache = new MetadataCache(); | 113 var cache = new MetadataCache(); |
90 cache.providers_.push(new FilesystemProvider()); | 114 cache.providers_.push(new FilesystemProvider()); |
91 cache.providers_.push(new GDataProvider()); | 115 cache.providers_.push(new GDataProvider()); |
| 116 cache.providers_.push(new ContentProvider()); |
92 return cache; | 117 return cache; |
93 }; | 118 }; |
94 | 119 |
95 /** | 120 /** |
| 121 * @return {boolean} Whether all providers are ready. |
| 122 */ |
| 123 MetadataCache.prototype.isInitialized = function() { |
| 124 for (var index = 0; index < this.providers_.length; index++) { |
| 125 if (!this.providers_[index].isInitialized()) return false; |
| 126 } |
| 127 return true; |
| 128 }; |
| 129 |
| 130 /** |
96 * Fetches the metadata, puts it in the cache, and passes to callback. | 131 * Fetches the metadata, puts it in the cache, and passes to callback. |
97 * If required metadata is already in the cache, does not fetch it again. | 132 * If required metadata is already in the cache, does not fetch it again. |
98 * @param {string|Entry|Array.<string|Entry>} items The list of entries or | 133 * @param {string|Entry|Array.<string|Entry>} items The list of entries or |
99 * file urls. May be just a single item. | 134 * file urls. May be just a single item. |
100 * @param {string} type The metadata type. | 135 * @param {string} type The metadata type. |
101 * @param {Function(Object)} callback The metadata is passed to callback. | 136 * @param {Function(Object)} callback The metadata is passed to callback. |
102 */ | 137 */ |
103 MetadataCache.prototype.get = function(items, type, callback) { | 138 MetadataCache.prototype.get = function(items, type, callback) { |
104 if (!(items instanceof Array)) { | 139 if (!(items instanceof Array)) { |
105 this.getOne(items, type, callback); | 140 this.getOne(items, type, callback); |
(...skipping 24 matching lines...) Expand all Loading... |
130 } | 165 } |
131 }; | 166 }; |
132 | 167 |
133 /** | 168 /** |
134 * Fetches the metadata for one Entry/FileUrl. See comments to |get|. | 169 * Fetches the metadata for one Entry/FileUrl. See comments to |get|. |
135 * @param {Entry|string} item The entry or url. | 170 * @param {Entry|string} item The entry or url. |
136 * @param {string} type Metadata type. | 171 * @param {string} type Metadata type. |
137 * @param {Function(Object)} callback The callback. | 172 * @param {Function(Object)} callback The callback. |
138 */ | 173 */ |
139 MetadataCache.prototype.getOne = function(item, type, callback) { | 174 MetadataCache.prototype.getOne = function(item, type, callback) { |
| 175 if (type.indexOf('|') != -1) { |
| 176 var types = type.split('|'); |
| 177 var result = {}; |
| 178 var typesLeft = types.length; |
| 179 |
| 180 function onOneType(requestedType, metadata) { |
| 181 result[requestedType] = metadata; |
| 182 typesLeft--; |
| 183 if (typesLeft == 0) callback(result); |
| 184 } |
| 185 |
| 186 for (var index = 0; index < types.length; index++) { |
| 187 this.getOne(item, types[index], onOneType.bind(null, types[index])); |
| 188 } |
| 189 return; |
| 190 } |
| 191 |
140 var url = this.itemToUrl_(item); | 192 var url = this.itemToUrl_(item); |
141 | 193 |
142 // Passing entry to fetchers may save one round-trip to APIs. | 194 // Passing entry to fetchers may save one round-trip to APIs. |
143 var fsEntry = item === url ? null : item; | 195 var fsEntry = item === url ? null : item; |
144 callback = callback || function() {}; | 196 callback = callback || function() {}; |
145 | 197 |
146 if (!(url in this.cache_)) | 198 if (!(url in this.cache_)) { |
147 this.cache_[url] = this.createEmptyEntry_(); | 199 this.cache_[url] = this.createEmptyEntry_(); |
| 200 this.totalCount_++; |
| 201 } |
148 | 202 |
149 var entry = this.cache_[url]; | 203 var entry = this.cache_[url]; |
150 | 204 |
151 if (type in entry.properties) { | 205 if (type in entry.properties) { |
152 callback(entry.properties[type]); | 206 callback(entry.properties[type]); |
153 return; | 207 return; |
154 } | 208 } |
155 | 209 |
156 this.startBatchUpdates(); | 210 this.startBatchUpdates(); |
157 var providers = this.providers_.slice(); | 211 var providers = this.providers_.slice(); |
158 var currentProvider; | 212 var currentProvider; |
159 var self = this; | 213 var self = this; |
160 | 214 |
161 function onFetched() { | 215 function onFetched() { |
162 if (type in entry.properties) { | 216 if (type in entry.properties) { |
163 self.endBatchUpdates(); | 217 self.endBatchUpdates(); |
164 // Got properties from provider. | 218 // Got properties from provider. |
165 callback(entry.properties[type]); | 219 callback(entry.properties[type]); |
166 } else { | 220 } else { |
167 tryNextProvider(); | 221 tryNextProvider(); |
168 } | 222 } |
169 } | 223 } |
170 | 224 |
171 function onProviderProperties(properties) { | 225 function onProviderProperties(properties) { |
172 var id = currentProvider.getId(); | 226 var id = currentProvider.getId(); |
173 var fetchedCallbacks = entry[id].callbacks; | 227 var fetchedCallbacks = entry[id].callbacks; |
174 delete entry[id].callbacks; | 228 delete entry[id].callbacks; |
175 entry[id].time = new Date(); | 229 entry.time = new Date(); |
176 self.mergeProperties_(url, properties); | 230 self.mergeProperties_(url, properties); |
177 | 231 |
178 for (var index = 0; index < fetchedCallbacks.length; index++) { | 232 for (var index = 0; index < fetchedCallbacks.length; index++) { |
179 fetchedCallbacks[index](); | 233 fetchedCallbacks[index](); |
180 } | 234 } |
181 } | 235 } |
182 | 236 |
183 function queryProvider() { | 237 function queryProvider() { |
184 var id = currentProvider.getId(); | 238 var id = currentProvider.getId(); |
185 if ('callbacks' in entry[id]) { | 239 if ('callbacks' in entry[id]) { |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
241 * @param {string} type The metadata type. | 295 * @param {string} type The metadata type. |
242 * @param {Array.<Object>} values List of corresponding metadata values. | 296 * @param {Array.<Object>} values List of corresponding metadata values. |
243 */ | 297 */ |
244 MetadataCache.prototype.set = function(items, type, values) { | 298 MetadataCache.prototype.set = function(items, type, values) { |
245 if (!(items instanceof Array)) | 299 if (!(items instanceof Array)) |
246 items = [items]; | 300 items = [items]; |
247 | 301 |
248 this.startBatchUpdates(); | 302 this.startBatchUpdates(); |
249 for (var index = 0; index < items.length; index++) { | 303 for (var index = 0; index < items.length; index++) { |
250 var url = this.itemToUrl_(items[index]); | 304 var url = this.itemToUrl_(items[index]); |
251 if (!(url in this.cache_)) | 305 if (!(url in this.cache_)) { |
252 this.cache_[url] = this.createEmptyEntry_(); | 306 this.cache_[url] = this.createEmptyEntry_(); |
| 307 this.totalCount_++; |
| 308 } |
253 this.cache_[url].properties[type] = values[index]; | 309 this.cache_[url].properties[type] = values[index]; |
254 this.notifyObservers_(url, type); | 310 this.notifyObservers_(url, type); |
255 } | 311 } |
256 this.endBatchUpdates(); | 312 this.endBatchUpdates(); |
257 }; | 313 }; |
258 | 314 |
259 /** | 315 /** |
260 * Clears the cached metadata values. | 316 * Clears the cached metadata values. |
261 * @param {string|Entry|Array.<string|Entry>} items The list of entries or | 317 * @param {string|Entry|Array.<string|Entry>} items The list of entries or |
262 * file urls. May be just a single item. | 318 * file urls. May be just a single item. |
263 * @param {string} type The metadata type. | 319 * @param {string} type The metadata type. |
264 */ | 320 */ |
265 MetadataCache.prototype.clear = function(items, type) { | 321 MetadataCache.prototype.clear = function(items, type) { |
266 if (!(items instanceof Array)) | 322 if (!(items instanceof Array)) |
267 items = [items]; | 323 items = [items]; |
268 | 324 |
| 325 var types = type.split('|'); |
| 326 |
269 for (var index = 0; index < items.length; index++) { | 327 for (var index = 0; index < items.length; index++) { |
270 var url = this.itemToUrl_(items[index]); | 328 var url = this.itemToUrl_(items[index]); |
271 if (url in this.cache_) | 329 if (url in this.cache_) { |
272 delete this.cache_[url].properties[type]; | 330 for (var j = 0; j < types.length; j++) { |
| 331 var type = types[j]; |
| 332 delete this.cache_[url].properties[type]; |
| 333 } |
| 334 } |
273 } | 335 } |
274 }; | 336 }; |
275 | 337 |
276 /** | 338 /** |
277 * Adds an observer, which will be notified when metadata changes. | 339 * Adds an observer, which will be notified when metadata changes. |
278 * @param {string|Entry} item The root item to look at. | 340 * @param {string|Entry} item The root item to look at. |
279 * @param {number} relation This defines, which items will trigger the observer. | 341 * @param {number} relation This defines, which items will trigger the observer. |
280 * See comments to |MetadataCache.EXACT| and others. | 342 * See comments to |MetadataCache.EXACT| and others. |
281 * @param {string} type The metadata type. | 343 * @param {string} type The metadata type. |
282 * @param {Function(Array.<string>, Array.<Object>)} observer List of file urls | 344 * @param {Function(Array.<string>, Array.<Object>)} observer List of file urls |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 } | 377 } |
316 } | 378 } |
317 return false; | 379 return false; |
318 }; | 380 }; |
319 | 381 |
320 /** | 382 /** |
321 * Start batch updates. | 383 * Start batch updates. |
322 */ | 384 */ |
323 MetadataCache.prototype.startBatchUpdates = function() { | 385 MetadataCache.prototype.startBatchUpdates = function() { |
324 this.batchCount_++; | 386 this.batchCount_++; |
| 387 if (this.batchCount_ == 1) |
| 388 this.lastBatchStart_ = new Date(); |
325 }; | 389 }; |
326 | 390 |
327 /** | 391 /** |
328 * End batch updates. Notifies observers if all nested updates are finished. | 392 * End batch updates. Notifies observers if all nested updates are finished. |
329 */ | 393 */ |
330 MetadataCache.prototype.endBatchUpdates = function() { | 394 MetadataCache.prototype.endBatchUpdates = function() { |
331 this.batchCount_--; | 395 this.batchCount_--; |
332 if (this.batchCount_ != 0) return; | 396 if (this.batchCount_ != 0) return; |
| 397 if (this.totalCount_ > MetadataCache.EVICTION_NUMBER) |
| 398 this.evict_(); |
333 for (var index = 0; index < this.observers_.length; index++) { | 399 for (var index = 0; index < this.observers_.length; index++) { |
334 var observer = this.observers_[index]; | 400 var observer = this.observers_[index]; |
335 var urls = []; | 401 var urls = []; |
336 var properties = []; | 402 var properties = []; |
337 for (var url in observer.pending) { | 403 for (var url in observer.pending) { |
338 if (observer.pending.hasOwnProperty(url) && url in this.cache_) { | 404 if (observer.pending.hasOwnProperty(url) && url in this.cache_) { |
339 urls.push(url); | 405 urls.push(url); |
340 properties.push(this.cache_[url].properties[observer.type] || null); | 406 properties.push(this.cache_[url].properties[observer.type] || null); |
341 } | 407 } |
342 } | 408 } |
(...skipping 18 matching lines...) Expand all Loading... |
361 // Observer expects array of urls and array of properties. | 427 // Observer expects array of urls and array of properties. |
362 observer.callback([url], [this.cache_[url].properties[type] || null]); | 428 observer.callback([url], [this.cache_[url].properties[type] || null]); |
363 } else { | 429 } else { |
364 observer.pending[url] = true; | 430 observer.pending[url] = true; |
365 } | 431 } |
366 } | 432 } |
367 } | 433 } |
368 }; | 434 }; |
369 | 435 |
370 /** | 436 /** |
| 437 * Removes the oldest items from the cache. |
| 438 * This method never removes the items from last batch. |
| 439 * @private |
| 440 */ |
| 441 MetadataCache.prototype.evict_ = function() { |
| 442 var toRemove = []; |
| 443 |
| 444 // We leave only a half of items, so we will not call evict_ soon again. |
| 445 var desiredCount = Math.round(MetadataCache.EVICTION_NUMBER / 2); |
| 446 var removeCount = this.totalCount_ - desiredCount; |
| 447 for (var url in this.cache_) { |
| 448 if (this.cache_.hasOwnProperty(url) && |
| 449 this.cache_[url].time < this.lastBatchStart_) { |
| 450 toRemove.push(url); |
| 451 } |
| 452 } |
| 453 |
| 454 toRemove.sort(function(a, b) { |
| 455 var aTime = this.cache_[a].time; |
| 456 var bTime = this.cache_[b].time; |
| 457 return aTime < bTime ? -1 : aTime > bTime ? 1 : 0; |
| 458 }); |
| 459 |
| 460 removeCount = Math.min(removeCount, toRemove.length); |
| 461 this.totalCount_ -= removeCount; |
| 462 for (var index = 0; index < removeCount; index++) { |
| 463 delete this.cache_[toRemove[index]]; |
| 464 } |
| 465 }; |
| 466 |
| 467 /** |
371 * Converts Entry or file url to url. | 468 * Converts Entry or file url to url. |
372 * @param {string|Entry} item Item to convert. | 469 * @param {string|Entry} item Item to convert. |
373 * @return {string} File url. | 470 * @return {string} File url. |
374 * @private | 471 * @private |
375 */ | 472 */ |
376 MetadataCache.prototype.itemToUrl_ = function(item) { | 473 MetadataCache.prototype.itemToUrl_ = function(item) { |
377 if (typeof(item) == 'string') | 474 if (typeof(item) == 'string') |
378 return item; | 475 return item; |
379 else | 476 else |
380 return item.toURL(); | 477 return item.toURL(); |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 * @return {boolean} Whether this provider provides this metadata. | 527 * @return {boolean} Whether this provider provides this metadata. |
431 */ | 528 */ |
432 MetadataProvider2.prototype.providesType = function(type) { return false; }; | 529 MetadataProvider2.prototype.providesType = function(type) { return false; }; |
433 | 530 |
434 /** | 531 /** |
435 * @return {string} Unique provider id. | 532 * @return {string} Unique provider id. |
436 */ | 533 */ |
437 MetadataProvider2.prototype.getId = function() { return ''; }; | 534 MetadataProvider2.prototype.getId = function() { return ''; }; |
438 | 535 |
439 /** | 536 /** |
| 537 * @return {boolean} Whether provider is ready. |
| 538 */ |
| 539 MetadataProvider2.prototype.isInitialized = function() { return true; }; |
| 540 |
| 541 /** |
440 * Fetches the metadata. It's suggested to return all the metadata this provider | 542 * Fetches the metadata. It's suggested to return all the metadata this provider |
441 * can fetch at once. | 543 * can fetch at once. |
442 * @param {string} url File url. | 544 * @param {string} url File url. |
443 * @param {string} type Requested metadata type. | 545 * @param {string} type Requested metadata type. |
444 * @param {Function(Object)} callback Callback expects a map from metadata type | 546 * @param {Function(Object)} callback Callback expects a map from metadata type |
445 * to metadata value. | 547 * to metadata value. |
446 * @param {Entry=} opt_entry The file entry if present. | 548 * @param {Entry=} opt_entry The file entry if present. |
447 */ | 549 */ |
448 MetadataProvider2.prototype.fetch = function(url, type, callback, opt_entry) { | 550 MetadataProvider2.prototype.fetch = function(url, type, callback, opt_entry) { |
449 throw new Error('Default metadata provider cannot fetch.'); | 551 throw new Error('Default metadata provider cannot fetch.'); |
450 }; | 552 }; |
451 | 553 |
452 | 554 |
453 /** | 555 /** |
454 * Provider of filesystem metadata. | 556 * Provider of filesystem metadata. |
455 * This provider returns the following objects: | 557 * This provider returns the following objects: |
456 * filesystem: { | 558 * filesystem: { size, modificationTime } |
457 * size; | |
458 * modificationTime; | |
459 * icon - string describing icon type; | |
460 * } | |
461 * @constructor | 559 * @constructor |
462 */ | 560 */ |
463 function FilesystemProvider() { | 561 function FilesystemProvider() { |
464 MetadataProvider2.call(this, 'filesystem'); | 562 MetadataProvider2.call(this); |
465 } | 563 } |
466 | 564 |
467 FilesystemProvider.prototype = { | 565 FilesystemProvider.prototype = { |
468 __proto__: MetadataProvider2.prototype | 566 __proto__: MetadataProvider2.prototype |
469 }; | 567 }; |
470 | 568 |
471 /** | 569 /** |
472 * @param {string} url The url. | 570 * @param {string} url The url. |
473 * @return {boolean} Whether this provider supports the url. | 571 * @return {boolean} Whether this provider supports the url. |
474 */ | 572 */ |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
519 onEntry(opt_entry); | 617 onEntry(opt_entry); |
520 else | 618 else |
521 webkitResolveLocalFileSystemURL(url, onEntry, onError); | 619 webkitResolveLocalFileSystemURL(url, onEntry, onError); |
522 }; | 620 }; |
523 | 621 |
524 /** | 622 /** |
525 * Provider of gdata metadata. | 623 * Provider of gdata metadata. |
526 * This provider returns the following objects: | 624 * This provider returns the following objects: |
527 * gdata: { pinned, hosted, present, dirty, editUrl, contentUrl } | 625 * gdata: { pinned, hosted, present, dirty, editUrl, contentUrl } |
528 * thumbnail: { url, transform } | 626 * thumbnail: { url, transform } |
| 627 * streaming: { url } |
529 * @constructor | 628 * @constructor |
530 */ | 629 */ |
531 function GDataProvider() { | 630 function GDataProvider() { |
532 MetadataProvider2.call(this, 'gdata'); | 631 MetadataProvider2.call(this); |
533 | 632 |
534 // We batch metadata fetches into single API call. | 633 // We batch metadata fetches into single API call. |
535 this.urls_ = []; | 634 this.urls_ = []; |
536 this.callbacks_ = []; | 635 this.callbacks_ = []; |
537 this.scheduled_ = false; | 636 this.scheduled_ = false; |
538 | 637 |
539 this.callApiBound_ = this.callApi_.bind(this); | 638 this.callApiBound_ = this.callApi_.bind(this); |
540 } | 639 } |
541 | 640 |
542 GDataProvider.prototype = { | 641 GDataProvider.prototype = { |
(...skipping 12 matching lines...) Expand all Loading... |
555 */ | 654 */ |
556 GDataProvider.prototype.supportsUrl = function(url) { | 655 GDataProvider.prototype.supportsUrl = function(url) { |
557 return GDataProvider.URL_PATTERN.test(url); | 656 return GDataProvider.URL_PATTERN.test(url); |
558 }; | 657 }; |
559 | 658 |
560 /** | 659 /** |
561 * @param {string} type The metadata type. | 660 * @param {string} type The metadata type. |
562 * @return {boolean} Whether this provider provides this metadata. | 661 * @return {boolean} Whether this provider provides this metadata. |
563 */ | 662 */ |
564 GDataProvider.prototype.providesType = function(type) { | 663 GDataProvider.prototype.providesType = function(type) { |
565 return type == 'gdata' || type == 'thumbnail'; | 664 return type == 'gdata' || type == 'thumbnail' || |
| 665 type == 'streaming' || type == 'media'; |
566 }; | 666 }; |
567 | 667 |
568 /** | 668 /** |
569 * @return {string} Unique provider id. | 669 * @return {string} Unique provider id. |
570 */ | 670 */ |
571 GDataProvider.prototype.getId = function() { return 'gdata'; }; | 671 GDataProvider.prototype.getId = function() { return 'gdata'; }; |
572 | 672 |
573 /** | 673 /** |
574 * Fetches the metadata. | 674 * Fetches the metadata. |
575 * @param {string} url File url. | 675 * @param {string} url File url. |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
641 result.gdata = { | 741 result.gdata = { |
642 present: data.isPresent, | 742 present: data.isPresent, |
643 pinned: data.isPinned, | 743 pinned: data.isPinned, |
644 hosted: data.isHosted, | 744 hosted: data.isHosted, |
645 dirty: data.isDirty, | 745 dirty: data.isDirty, |
646 availableOffline: GDataProvider.isAvailableOffline(data), | 746 availableOffline: GDataProvider.isAvailableOffline(data), |
647 availableWhenMetered: GDataProvider.isAvailableWhenMetered(data), | 747 availableWhenMetered: GDataProvider.isAvailableWhenMetered(data), |
648 contentUrl: (data.contentUrl || '').replace(/\?.*$/gi, ''), | 748 contentUrl: (data.contentUrl || '').replace(/\?.*$/gi, ''), |
649 editUrl: data.editUrl || '' | 749 editUrl: data.editUrl || '' |
650 }; | 750 }; |
| 751 |
| 752 if (!data.isPresent) { |
| 753 // Block the local fetch for gdata files, which require downloading. |
| 754 result.thumbnail = { url: '', transform: null }; |
| 755 result.media = {}; |
| 756 } |
| 757 |
651 if ('thumbnailUrl' in data) { | 758 if ('thumbnailUrl' in data) { |
652 result.thumbnail = { | 759 result.thumbnail = { |
653 url: data.thumbnailUrl, | 760 url: data.thumbnailUrl, |
654 transform: '' | 761 transform: null |
655 }; | 762 }; |
656 } | 763 } |
| 764 if (data.isPresent && ('contentUrl' in data)) { |
| 765 result.streaming = { |
| 766 url: data.contentUrl.replace(/\?.*$/gi, '') |
| 767 }; |
| 768 } |
657 return result; | 769 return result; |
658 }; | 770 }; |
| 771 |
| 772 |
| 773 /** |
| 774 * Provider of content metadata. |
| 775 * This provider returns the following objects: |
| 776 * thumbnail: { url, transform } |
| 777 * media: { artist, album, title, width, height, imageTransform, etc. } |
| 778 * fetchedMedia: { same fields here } |
| 779 * @constructor |
| 780 */ |
| 781 function ContentProvider() { |
| 782 MetadataProvider2.call(this); |
| 783 |
| 784 // Pass all URLs to the metadata reader until we have a correct filter. |
| 785 this.urlFilter_ = /.*/; |
| 786 |
| 787 var path = document.location.pathname; |
| 788 var workerPath = document.location.origin + |
| 789 path.substring(0, path.lastIndexOf('/') + 1) + |
| 790 'js/metadata/metadata_dispatcher.js'; |
| 791 |
| 792 this.dispatcher_ = new Worker(workerPath); |
| 793 this.dispatcher_.onmessage = this.onMessage_.bind(this); |
| 794 this.dispatcher_.postMessage({verb: 'init'}); |
| 795 |
| 796 // Initialization is not complete until the Worker sends back the |
| 797 // 'initialized' message. See below. |
| 798 this.initialized_ = false; |
| 799 |
| 800 // Map from url to callback. |
| 801 // Note that simultaneous requests for same url are handled in MetadataCache. |
| 802 this.callbacks_ = {}; |
| 803 } |
| 804 |
| 805 ContentProvider.prototype = { |
| 806 __proto__: MetadataProvider2.prototype |
| 807 }; |
| 808 |
| 809 /** |
| 810 * @param {string} url The url. |
| 811 * @return {boolean} Whether this provider supports the url. |
| 812 */ |
| 813 ContentProvider.prototype.supportsUrl = function(url) { |
| 814 return url.match(this.urlFilter_); |
| 815 }; |
| 816 |
| 817 /** |
| 818 * @param {string} type The metadata type. |
| 819 * @return {boolean} Whether this provider provides this metadata. |
| 820 */ |
| 821 ContentProvider.prototype.providesType = function(type) { |
| 822 return type == 'thumbnail' || type == 'fetchedMedia' || type == 'media'; |
| 823 }; |
| 824 |
| 825 /** |
| 826 * @return {string} Unique provider id. |
| 827 */ |
| 828 ContentProvider.prototype.getId = function() { return 'content'; }; |
| 829 |
| 830 /** |
| 831 * Fetches the metadata. |
| 832 * @param {string} url File url. |
| 833 * @param {string} type Requested metadata type. |
| 834 * @param {Function(Object)} callback Callback expects a map from metadata type |
| 835 * to metadata value. |
| 836 * @param {Entry=} opt_entry The file entry if present. |
| 837 */ |
| 838 ContentProvider.prototype.fetch = function(url, type, callback, opt_entry) { |
| 839 if (opt_entry && opt_entry.isDirectory) { |
| 840 callback({}); |
| 841 return; |
| 842 } |
| 843 this.callbacks_[url] = callback; |
| 844 this.dispatcher_.postMessage({verb: 'request', arguments: [url]}); |
| 845 }; |
| 846 |
| 847 /** |
| 848 * Dispatch a message from a metadata reader to the appropriate on* method. |
| 849 * @param {Object} event The event. |
| 850 * @private |
| 851 */ |
| 852 ContentProvider.prototype.onMessage_ = function(event) { |
| 853 var data = event.data; |
| 854 |
| 855 var methodName = |
| 856 'on' + data.verb.substr(0, 1).toUpperCase() + data.verb.substr(1) + '_'; |
| 857 |
| 858 if (!(methodName in this)) { |
| 859 console.log('Unknown message from metadata reader: ' + data.verb, data); |
| 860 return; |
| 861 } |
| 862 |
| 863 this[methodName].apply(this, data.arguments); |
| 864 }; |
| 865 |
| 866 /** |
| 867 * @return {boolean} Whether provider is ready. |
| 868 */ |
| 869 ContentProvider.prototype.isInitialized = function() { |
| 870 return this.initialized_; |
| 871 }; |
| 872 |
| 873 /** |
| 874 * Handles the 'initialized' message from the metadata reader Worker. |
| 875 * @param {Object} regexp Regexp of supported urls. |
| 876 * @private |
| 877 */ |
| 878 ContentProvider.prototype.onInitialized_ = function(regexp) { |
| 879 this.urlFilter_ = regexp; |
| 880 |
| 881 // Tests can monitor for this state with |
| 882 // ExtensionTestMessageListener listener("worker-initialized"); |
| 883 // ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| 884 // Automated tests need to wait for this, otherwise we crash in |
| 885 // browser_test cleanup because the worker process still has |
| 886 // URL requests in-flight. |
| 887 chrome.test.sendMessage('worker-initialized'); |
| 888 this.initialized_ = true; |
| 889 }; |
| 890 |
| 891 /** |
| 892 * Handles the 'result' message from the worker. |
| 893 * @param {string} url File url. |
| 894 * @param {Object} metadata The metadata. |
| 895 * @private |
| 896 */ |
| 897 ContentProvider.prototype.onResult_ = function(url, metadata) { |
| 898 var callback = this.callbacks_[url]; |
| 899 delete this.callbacks_[url]; |
| 900 |
| 901 var result = {}; |
| 902 |
| 903 if ('thumbnailURL' in metadata) { |
| 904 metadata.thumbnailTransform = metadata.thumbnailTransform || null; |
| 905 result.thumbnail = { |
| 906 url: metadata.thumbnailURL, |
| 907 transform: metadata.thumbnailTransform |
| 908 }; |
| 909 delete metadata.thumbnailURL; |
| 910 delete metadata.thumbnailTransform; |
| 911 } |
| 912 |
| 913 for (var key in metadata) { |
| 914 if (metadata.hasOwnProperty(key)) { |
| 915 if (!('media' in result)) result.media = {}; |
| 916 result.media[key] = metadata[key]; |
| 917 } |
| 918 } |
| 919 |
| 920 if ('media' in result) { |
| 921 result.fetchedMedia = result.media; |
| 922 } |
| 923 |
| 924 callback(result); |
| 925 }; |
| 926 |
| 927 /** |
| 928 * Handles the 'error' message from the worker. |
| 929 * @param {string} url File url. |
| 930 * @param {string} step Step failed. |
| 931 * @param {string} error Error description. |
| 932 * @param {Object?} metadata The metadata, if available. |
| 933 * @private |
| 934 */ |
| 935 ContentProvider.prototype.onError_ = function(url, step, error, metadata) { |
| 936 console.warn('metadata: ' + url + ': ' + step + ': ' + error); |
| 937 metadata = metadata || {}; |
| 938 // Prevent asking for thumbnail again. |
| 939 metadata.thumbnailURL = ''; |
| 940 this.onResult_(url, metadata); |
| 941 }; |
| 942 |
| 943 /** |
| 944 * Handles the 'log' message from the worker. |
| 945 * @param {Array.<*>} arglist Log arguments. |
| 946 * @private |
| 947 */ |
| 948 ContentProvider.prototype.onLog_ = function(arglist) { |
| 949 console.log.apply(console, ['metadata:'].concat(arglist)); |
| 950 }; |
OLD | NEW |