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