Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(385)

Side by Side Diff: chrome/browser/resources/file_manager/js/metadata/metadata_cache.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8 * MetadataCache is a map from url to an object containing properties.
9 * Properties are divided by types, and all properties of one type are accessed
10 * at once.
11 * Some of the properties:
12 * {
13 * filesystem: size, modificationTime
14 * internal: presence
15 * drive: pinned, present, hosted, availableOffline
16 * streaming: (no property)
17 *
18 * Following are not fetched for non-present drive files.
19 * media: artist, album, title, width, height, imageTransform, etc.
20 * thumbnail: url, transform
21 *
22 * Following are always fetched from content, and so force the downloading
23 * of remote drive files. One should use this for required content metadata,
24 * i.e. image orientation.
25 * fetchedMedia: width, height, etc.
26 * }
27 *
28 * Typical usages:
29 * {
30 * cache.get([entry1, entry2], 'drive|filesystem', function(metadata) {
31 * if (metadata[0].drive.pinned && metadata[1].filesystem.size == 0)
32 * alert("Pinned and empty!");
33 * });
34 *
35 * cache.set(entry, 'internal', {presence: 'deleted'});
36 *
37 * cache.clear([fileUrl1, fileUrl2], 'filesystem');
38 *
39 * // Getting fresh value.
40 * cache.clear(entry, 'thumbnail');
41 * cache.get(entry, 'thumbnail', function(thumbnail) {
42 * img.src = thumbnail.url;
43 * });
44 *
45 * var cached = cache.getCached(entry, 'filesystem');
46 * var size = (cached && cached.size) || UNKNOWN_SIZE;
47 * }
48 *
49 * @constructor
50 */
51 function MetadataCache() {
52 /**
53 * Map from urls to entries. Entry contains |properties| - an hierarchical
54 * object of values, and an object for each metadata provider:
55 * <prodiver-id>: { time, callbacks }
56 * @private
57 */
58 this.cache_ = {};
59
60 /**
61 * List of metadata providers.
62 * @private
63 */
64 this.providers_ = [];
65
66 /**
67 * List of observers added. Each one is an object with fields:
68 * re - regexp of urls;
69 * type - metadata type;
70 * callback - the callback.
71 * TODO(dgozman): pass entries to observer if present.
72 * @private
73 */
74 this.observers_ = [];
75 this.observerId_ = 0;
76
77 this.batchCount_ = 0;
78 this.totalCount_ = 0;
79
80 /**
81 * Time of first get query of the current batch. Items updated later than this
82 * will not be evicted.
83 * @private
84 */
85 this.lastBatchStart_ = new Date();
86 }
87
88 /**
89 * Observer type: it will be notified if the url changed is exactly the same
90 * as the url passed.
91 */
92 MetadataCache.EXACT = 0;
93
94 /**
95 * Observer type: it will be notified if the url changed is an immediate child
96 * of the url passed.
97 */
98 MetadataCache.CHILDREN = 1;
99
100 /**
101 * Observer type: it will be notified if the url changed is any descendant
102 * of the url passed.
103 */
104 MetadataCache.DESCENDANTS = 2;
105
106 /**
107 * Minimum number of items in cache to start eviction.
108 */
109 MetadataCache.EVICTION_NUMBER = 1000;
110
111 /**
112 * @return {MetadataCache!} The cache with all providers.
113 */
114 MetadataCache.createFull = function() {
115 var cache = new MetadataCache();
116 cache.providers_.push(new FilesystemProvider());
117 cache.providers_.push(new DriveProvider());
118 cache.providers_.push(new ContentProvider());
119 return cache;
120 };
121
122 /**
123 * Clones metadata entry. Metadata entries may contain scalars, arrays,
124 * hash arrays and Date object. Other objects are not supported.
125 * @param {Object} metadata Metadata object.
126 * @return {Object} Cloned entry.
127 */
128 MetadataCache.cloneMetadata = function(metadata) {
129 if (metadata instanceof Array) {
130 var result = [];
131 for (var index = 0; index < metadata.length; index++) {
132 result[index] = MetadataCache.cloneMetadata(metadata[index]);
133 }
134 return result;
135 } else if (metadata instanceof Date) {
136 var result = new Date();
137 result.setTime(metadata.getTime());
138 return result;
139 } else if (metadata instanceof Object) { // Hash array only.
140 var result = {};
141 for (var property in metadata) {
142 if (metadata.hasOwnProperty(property))
143 result[property] = MetadataCache.cloneMetadata(metadata[property]);
144 }
145 return result;
146 } else {
147 return metadata;
148 }
149 };
150
151 /**
152 * @return {boolean} Whether all providers are ready.
153 */
154 MetadataCache.prototype.isInitialized = function() {
155 for (var index = 0; index < this.providers_.length; index++) {
156 if (!this.providers_[index].isInitialized()) return false;
157 }
158 return true;
159 };
160
161 /**
162 * Fetches the metadata, puts it in the cache, and passes to callback.
163 * If required metadata is already in the cache, does not fetch it again.
164 * @param {string|Entry|Array.<string|Entry>} items The list of entries or
165 * file urls. May be just a single item.
166 * @param {string} type The metadata type.
167 * @param {function(Object)} callback The metadata is passed to callback.
168 */
169 MetadataCache.prototype.get = function(items, type, callback) {
170 if (!(items instanceof Array)) {
171 this.getOne(items, type, callback);
172 return;
173 }
174
175 if (items.length == 0) {
176 if (callback) callback([]);
177 return;
178 }
179
180 var result = [];
181 var remaining = items.length;
182 this.startBatchUpdates();
183
184 var onOneItem = function(index, value) {
185 result[index] = value;
186 remaining--;
187 if (remaining == 0) {
188 this.endBatchUpdates();
189 if (callback) setTimeout(callback, 0, result);
190 }
191 };
192
193 for (var index = 0; index < items.length; index++) {
194 result.push(null);
195 this.getOne(items[index], type, onOneItem.bind(this, index));
196 }
197 };
198
199 /**
200 * Fetches the metadata for one Entry/FileUrl. See comments to |get|.
201 * @param {Entry|string} item The entry or url.
202 * @param {string} type Metadata type.
203 * @param {function(Object)} callback The callback.
204 */
205 MetadataCache.prototype.getOne = function(item, type, callback) {
206 if (type.indexOf('|') != -1) {
207 var types = type.split('|');
208 var result = {};
209 var typesLeft = types.length;
210
211 var onOneType = function(requestedType, metadata) {
212 result[requestedType] = metadata;
213 typesLeft--;
214 if (typesLeft == 0) callback(result);
215 };
216
217 for (var index = 0; index < types.length; index++) {
218 this.getOne(item, types[index], onOneType.bind(null, types[index]));
219 }
220 return;
221 }
222
223 var url = this.itemToUrl_(item);
224
225 // Passing entry to fetchers may save one round-trip to APIs.
226 var fsEntry = item === url ? null : item;
227 callback = callback || function() {};
228
229 if (!(url in this.cache_)) {
230 this.cache_[url] = this.createEmptyEntry_();
231 this.totalCount_++;
232 }
233
234 var entry = this.cache_[url];
235
236 if (type in entry.properties) {
237 callback(entry.properties[type]);
238 return;
239 }
240
241 this.startBatchUpdates();
242 var providers = this.providers_.slice();
243 var currentProvider;
244 var self = this;
245
246 var onFetched = function() {
247 if (type in entry.properties) {
248 self.endBatchUpdates();
249 // Got properties from provider.
250 callback(entry.properties[type]);
251 } else {
252 tryNextProvider();
253 }
254 };
255
256 var onProviderProperties = function(properties) {
257 var id = currentProvider.getId();
258 var fetchedCallbacks = entry[id].callbacks;
259 delete entry[id].callbacks;
260 entry.time = new Date();
261 self.mergeProperties_(url, properties);
262
263 for (var index = 0; index < fetchedCallbacks.length; index++) {
264 fetchedCallbacks[index]();
265 }
266 };
267
268 var queryProvider = function() {
269 var id = currentProvider.getId();
270 if ('callbacks' in entry[id]) {
271 // We are querying this provider now.
272 entry[id].callbacks.push(onFetched);
273 } else {
274 entry[id].callbacks = [onFetched];
275 currentProvider.fetch(url, type, onProviderProperties, fsEntry);
276 }
277 };
278
279 var tryNextProvider = function() {
280 if (providers.length == 0) {
281 self.endBatchUpdates();
282 callback(entry.properties[type] || null);
283 return;
284 }
285
286 currentProvider = providers.shift();
287 if (currentProvider.supportsUrl(url) &&
288 currentProvider.providesType(type)) {
289 queryProvider();
290 } else {
291 tryNextProvider();
292 }
293 };
294
295 tryNextProvider();
296 };
297
298 /**
299 * Returns the cached metadata value, or |null| if not present.
300 * @param {string|Entry|Array.<string|Entry>} items The list of entries or
301 * file urls. May be just a single item.
302 * @param {string} type The metadata type.
303 * @return {Object} The metadata or null.
304 */
305 MetadataCache.prototype.getCached = function(items, type) {
306 var single = false;
307 if (!(items instanceof Array)) {
308 single = true;
309 items = [items];
310 }
311
312 var result = [];
313 for (var index = 0; index < items.length; index++) {
314 var url = this.itemToUrl_(items[index]);
315 result.push(url in this.cache_ ?
316 (this.cache_[url].properties[type] || null) : null);
317 }
318
319 return single ? result[0] : result;
320 };
321
322 /**
323 * Puts the metadata into cache
324 * @param {string|Entry|Array.<string|Entry>} items The list of entries or
325 * file urls. May be just a single item.
326 * @param {string} type The metadata type.
327 * @param {Array.<Object>} values List of corresponding metadata values.
328 */
329 MetadataCache.prototype.set = function(items, type, values) {
330 if (!(items instanceof Array)) {
331 items = [items];
332 values = [values];
333 }
334
335 this.startBatchUpdates();
336 for (var index = 0; index < items.length; index++) {
337 var url = this.itemToUrl_(items[index]);
338 if (!(url in this.cache_)) {
339 this.cache_[url] = this.createEmptyEntry_();
340 this.totalCount_++;
341 }
342 this.cache_[url].properties[type] = values[index];
343 this.notifyObservers_(url, type);
344 }
345 this.endBatchUpdates();
346 };
347
348 /**
349 * Clears the cached metadata values.
350 * @param {string|Entry|Array.<string|Entry>} items The list of entries or
351 * file urls. May be just a single item.
352 * @param {string} type The metadata types or * for any type.
353 */
354 MetadataCache.prototype.clear = function(items, type) {
355 if (!(items instanceof Array))
356 items = [items];
357
358 var types = type.split('|');
359
360 for (var index = 0; index < items.length; index++) {
361 var url = this.itemToUrl_(items[index]);
362 if (url in this.cache_) {
363 if (type === '*') {
364 this.cache_[url].properties = {};
365 } else {
366 for (var j = 0; j < types.length; j++) {
367 var type = types[j];
368 delete this.cache_[url].properties[type];
369 }
370 }
371 }
372 }
373 };
374
375 /**
376 * Clears the cached metadata values recursively.
377 * @param {Entry|string} item An entry or a url.
378 * @param {string} type The metadata types or * for any type.
379 */
380 MetadataCache.prototype.clearRecursively = function(item, type) {
381 var types = type.split('|');
382 var keys = Object.keys(this.cache_);
383 var url = this.itemToUrl_(item);
384
385 for (var index = 0; index < keys.length; index++) {
386 var entryUrl = keys[index];
387 if (entryUrl.substring(0, url.length) === url) {
388 if (type === '*') {
389 this.cache_[entryUrl].properties = {};
390 } else {
391 for (var j = 0; j < types.length; j++) {
392 var type = types[j];
393 delete this.cache_[entryUrl].properties[type];
394 }
395 }
396 }
397 }
398 };
399
400 /**
401 * Adds an observer, which will be notified when metadata changes.
402 * @param {string|Entry} item The root item to look at.
403 * @param {number} relation This defines, which items will trigger the observer.
404 * See comments to |MetadataCache.EXACT| and others.
405 * @param {string} type The metadata type.
406 * @param {function(Array.<string>, Array.<Object>)} observer List of file urls
407 * and corresponding metadata values are passed to this callback.
408 * @return {number} The observer id, which can be used to remove it.
409 */
410 MetadataCache.prototype.addObserver = function(item, relation, type, observer) {
411 var url = this.itemToUrl_(item);
412 var re = url;
413 if (relation == MetadataCache.CHILDREN) {
414 re += '(/[^/]*)?';
415 } else if (relation == MetadataCache.DESCENDANTS) {
416 re += '(/.*)?';
417 }
418 var id = ++this.observerId_;
419 this.observers_.push({
420 re: new RegExp('^' + re + '$'),
421 type: type,
422 callback: observer,
423 id: id,
424 pending: {}
425 });
426 return id;
427 };
428
429 /**
430 * Removes the observer.
431 * @param {number} id Observer id.
432 * @return {boolean} Whether observer was removed or not.
433 */
434 MetadataCache.prototype.removeObserver = function(id) {
435 for (var index = 0; index < this.observers_.length; index++) {
436 if (this.observers_[index].id == id) {
437 this.observers_.splice(index, 1);
438 return true;
439 }
440 }
441 return false;
442 };
443
444 /**
445 * Start batch updates.
446 */
447 MetadataCache.prototype.startBatchUpdates = function() {
448 this.batchCount_++;
449 if (this.batchCount_ == 1)
450 this.lastBatchStart_ = new Date();
451 };
452
453 /**
454 * End batch updates. Notifies observers if all nested updates are finished.
455 */
456 MetadataCache.prototype.endBatchUpdates = function() {
457 this.batchCount_--;
458 if (this.batchCount_ != 0) return;
459 if (this.totalCount_ > MetadataCache.EVICTION_NUMBER)
460 this.evict_();
461 for (var index = 0; index < this.observers_.length; index++) {
462 var observer = this.observers_[index];
463 var urls = [];
464 var properties = [];
465 for (var url in observer.pending) {
466 if (observer.pending.hasOwnProperty(url) && url in this.cache_) {
467 urls.push(url);
468 properties.push(this.cache_[url].properties[observer.type] || null);
469 }
470 }
471 observer.pending = {};
472 if (urls.length > 0) {
473 observer.callback(urls, properties);
474 }
475 }
476 };
477
478 /**
479 * Notifies observers or puts the data to pending list.
480 * @param {string} url Url of entry changed.
481 * @param {string} type Metadata type.
482 * @private
483 */
484 MetadataCache.prototype.notifyObservers_ = function(url, type) {
485 for (var index = 0; index < this.observers_.length; index++) {
486 var observer = this.observers_[index];
487 if (observer.type == type && observer.re.test(url)) {
488 if (this.batchCount_ == 0) {
489 // Observer expects array of urls and array of properties.
490 observer.callback([url], [this.cache_[url].properties[type] || null]);
491 } else {
492 observer.pending[url] = true;
493 }
494 }
495 }
496 };
497
498 /**
499 * Removes the oldest items from the cache.
500 * This method never removes the items from last batch.
501 * @private
502 */
503 MetadataCache.prototype.evict_ = function() {
504 var toRemove = [];
505
506 // We leave only a half of items, so we will not call evict_ soon again.
507 var desiredCount = Math.round(MetadataCache.EVICTION_NUMBER / 2);
508 var removeCount = this.totalCount_ - desiredCount;
509 for (var url in this.cache_) {
510 if (this.cache_.hasOwnProperty(url) &&
511 this.cache_[url].time < this.lastBatchStart_) {
512 toRemove.push(url);
513 }
514 }
515
516 toRemove.sort(function(a, b) {
517 var aTime = this.cache_[a].time;
518 var bTime = this.cache_[b].time;
519 return aTime < bTime ? -1 : aTime > bTime ? 1 : 0;
520 }.bind(this));
521
522 removeCount = Math.min(removeCount, toRemove.length);
523 this.totalCount_ -= removeCount;
524 for (var index = 0; index < removeCount; index++) {
525 delete this.cache_[toRemove[index]];
526 }
527 };
528
529 /**
530 * Converts Entry or file url to url.
531 * @param {string|Entry} item Item to convert.
532 * @return {string} File url.
533 * @private
534 */
535 MetadataCache.prototype.itemToUrl_ = function(item) {
536 if (typeof(item) == 'string')
537 return item;
538
539 if (!item._URL_) {
540 // Is a fake entry.
541 if (typeof item.toURL !== 'function')
542 item._URL_ = util.makeFilesystemUrl(item.fullPath);
543 else
544 item._URL_ = item.toURL();
545 }
546
547 return item._URL_;
548 };
549
550 /**
551 * @return {Object} Empty cache entry.
552 * @private
553 */
554 MetadataCache.prototype.createEmptyEntry_ = function() {
555 var entry = {properties: {}};
556 for (var index = 0; index < this.providers_.length; index++) {
557 entry[this.providers_[index].getId()] = {};
558 }
559 return entry;
560 };
561
562 /**
563 * Caches all the properties from data to cache entry for url.
564 * @param {string} url The url.
565 * @param {Object} data The properties.
566 * @private
567 */
568 MetadataCache.prototype.mergeProperties_ = function(url, data) {
569 if (data == null) return;
570 var properties = this.cache_[url].properties;
571 for (var type in data) {
572 if (data.hasOwnProperty(type) && !properties.hasOwnProperty(type)) {
573 properties[type] = data[type];
574 this.notifyObservers_(url, type);
575 }
576 }
577 };
578
579 /**
580 * Base class for metadata providers.
581 * @constructor
582 */
583 function MetadataProvider() {
584 }
585
586 /**
587 * @param {string} url The url.
588 * @return {boolean} Whether this provider supports the url.
589 */
590 MetadataProvider.prototype.supportsUrl = function(url) { return false; };
591
592 /**
593 * @param {string} type The metadata type.
594 * @return {boolean} Whether this provider provides this metadata.
595 */
596 MetadataProvider.prototype.providesType = function(type) { return false; };
597
598 /**
599 * @return {string} Unique provider id.
600 */
601 MetadataProvider.prototype.getId = function() { return ''; };
602
603 /**
604 * @return {boolean} Whether provider is ready.
605 */
606 MetadataProvider.prototype.isInitialized = function() { return true; };
607
608 /**
609 * Fetches the metadata. It's suggested to return all the metadata this provider
610 * can fetch at once.
611 * @param {string} url File url.
612 * @param {string} type Requested metadata type.
613 * @param {function(Object)} callback Callback expects a map from metadata type
614 * to metadata value.
615 * @param {Entry=} opt_entry The file entry if present.
616 */
617 MetadataProvider.prototype.fetch = function(url, type, callback, opt_entry) {
618 throw new Error('Default metadata provider cannot fetch.');
619 };
620
621
622 /**
623 * Provider of filesystem metadata.
624 * This provider returns the following objects:
625 * filesystem: { size, modificationTime }
626 * @constructor
627 */
628 function FilesystemProvider() {
629 MetadataProvider.call(this);
630 }
631
632 FilesystemProvider.prototype = {
633 __proto__: MetadataProvider.prototype
634 };
635
636 /**
637 * @param {string} url The url.
638 * @return {boolean} Whether this provider supports the url.
639 */
640 FilesystemProvider.prototype.supportsUrl = function(url) {
641 return true;
642 };
643
644 /**
645 * @param {string} type The metadata type.
646 * @return {boolean} Whether this provider provides this metadata.
647 */
648 FilesystemProvider.prototype.providesType = function(type) {
649 return type == 'filesystem';
650 };
651
652 /**
653 * @return {string} Unique provider id.
654 */
655 FilesystemProvider.prototype.getId = function() { return 'filesystem'; };
656
657 /**
658 * Fetches the metadata.
659 * @param {string} url File url.
660 * @param {string} type Requested metadata type.
661 * @param {function(Object)} callback Callback expects a map from metadata type
662 * to metadata value.
663 * @param {Entry=} opt_entry The file entry if present.
664 */
665 FilesystemProvider.prototype.fetch = function(url, type, callback, opt_entry) {
666 function onError(error) {
667 callback(null);
668 }
669
670 function onMetadata(entry, metadata) {
671 callback({
672 filesystem: {
673 size: entry.isFile ? (metadata.size || 0) : -1,
674 modificationTime: metadata.modificationTime
675 }
676 });
677 }
678
679 function onEntry(entry) {
680 entry.getMetadata(onMetadata.bind(null, entry), onError);
681 }
682
683 if (opt_entry)
684 onEntry(opt_entry);
685 else
686 window.webkitResolveLocalFileSystemURL(url, onEntry, onError);
687 };
688
689 /**
690 * Provider of drive metadata.
691 * This provider returns the following objects:
692 * drive: { pinned, hosted, present, customIconUrl, etc. }
693 * thumbnail: { url, transform }
694 * streaming: { }
695 * @constructor
696 */
697 function DriveProvider() {
698 MetadataProvider.call(this);
699
700 // We batch metadata fetches into single API call.
701 this.urls_ = [];
702 this.callbacks_ = [];
703 this.scheduled_ = false;
704
705 this.callApiBound_ = this.callApi_.bind(this);
706 }
707
708 DriveProvider.prototype = {
709 __proto__: MetadataProvider.prototype
710 };
711
712 /**
713 * @param {string} url The url.
714 * @return {boolean} Whether this provider supports the url.
715 */
716 DriveProvider.prototype.supportsUrl = function(url) {
717 return FileType.isOnDrive(url);
718 };
719
720 /**
721 * @param {string} type The metadata type.
722 * @return {boolean} Whether this provider provides this metadata.
723 */
724 DriveProvider.prototype.providesType = function(type) {
725 return type == 'drive' || type == 'thumbnail' ||
726 type == 'streaming' || type == 'media';
727 };
728
729 /**
730 * @return {string} Unique provider id.
731 */
732 DriveProvider.prototype.getId = function() { return 'drive'; };
733
734 /**
735 * Fetches the metadata.
736 * @param {string} url File url.
737 * @param {string} type Requested metadata type.
738 * @param {function(Object)} callback Callback expects a map from metadata type
739 * to metadata value.
740 * @param {Entry=} opt_entry The file entry if present.
741 */
742 DriveProvider.prototype.fetch = function(url, type, callback, opt_entry) {
743 this.urls_.push(url);
744 this.callbacks_.push(callback);
745 if (!this.scheduled_) {
746 this.scheduled_ = true;
747 setTimeout(this.callApiBound_, 0);
748 }
749 };
750
751 /**
752 * Schedules the API call.
753 * @private
754 */
755 DriveProvider.prototype.callApi_ = function() {
756 this.scheduled_ = false;
757
758 var urls = this.urls_;
759 var callbacks = this.callbacks_;
760 this.urls_ = [];
761 this.callbacks_ = [];
762 var self = this;
763
764 var task = function(url, callback) {
765 chrome.fileBrowserPrivate.getDriveEntryProperties(url,
766 function(properties) {
767 callback(self.convert_(properties, url));
768 });
769 };
770
771 for (var i = 0; i < urls.length; i++)
772 task(urls[i], callbacks[i]);
773 };
774
775 /**
776 * @param {DriveEntryProperties} data Drive entry properties.
777 * @param {string} url File url.
778 * @return {boolean} True if the file is available offline.
779 */
780 DriveProvider.isAvailableOffline = function(data, url) {
781 if (data.isPresent)
782 return true;
783
784 if (!data.isHosted)
785 return false;
786
787 // What's available offline? See the 'Web' column at:
788 // http://support.google.com/drive/bin/answer.py?hl=en&answer=1628467
789 var subtype = FileType.getType(url).subtype;
790 return (subtype == 'doc' ||
791 subtype == 'draw' ||
792 subtype == 'sheet' ||
793 subtype == 'slides');
794 };
795
796 /**
797 * @param {DriveEntryProperties} data Drive entry properties.
798 * @return {boolean} True if opening the file does not require downloading it
799 * via a metered connection.
800 */
801 DriveProvider.isAvailableWhenMetered = function(data) {
802 return data.isPresent || data.isHosted;
803 };
804
805 /**
806 * Converts API metadata to internal format.
807 * @param {Object} data Metadata from API call.
808 * @param {string} url File url.
809 * @return {Object} Metadata in internal format.
810 * @private
811 */
812 DriveProvider.prototype.convert_ = function(data, url) {
813 var result = {};
814 result.drive = {
815 present: data.isPresent,
816 pinned: data.isPinned,
817 hosted: data.isHosted,
818 imageWidth: data.imageWidth,
819 imageHeight: data.imageHeight,
820 imageRotation: data.imageRotation,
821 availableOffline: DriveProvider.isAvailableOffline(data, url),
822 availableWhenMetered: DriveProvider.isAvailableWhenMetered(data),
823 customIconUrl: data.customIconUrl || '',
824 contentMimeType: data.contentMimeType || '',
825 sharedWithMe: data.sharedWithMe
826 };
827
828 if (!data.isPresent) {
829 // Block the local fetch for drive files, which require downloading.
830 result.thumbnail = {url: '', transform: null};
831 result.media = {};
832 }
833
834 if ('thumbnailUrl' in data) {
835 result.thumbnail = {
836 url: data.thumbnailUrl.replace(/s220/, 's500'),
837 transform: null
838 };
839 }
840 if (!data.isPresent) {
841 // Indicate that the data is not available in local cache.
842 // It used to have a field 'url' for streaming play, but it is
843 // derprecated. See crbug.com/174560.
844 result.streaming = {};
845 }
846 return result;
847 };
848
849
850 /**
851 * Provider of content metadata.
852 * This provider returns the following objects:
853 * thumbnail: { url, transform }
854 * media: { artist, album, title, width, height, imageTransform, etc. }
855 * fetchedMedia: { same fields here }
856 * @constructor
857 */
858 function ContentProvider() {
859 MetadataProvider.call(this);
860
861 // Pass all URLs to the metadata reader until we have a correct filter.
862 this.urlFilter_ = /.*/;
863
864 var path = document.location.pathname;
865 var workerPath = document.location.origin +
866 path.substring(0, path.lastIndexOf('/') + 1) +
867 'js/metadata/metadata_dispatcher.js';
868
869 if (ContentProvider.USE_SHARED_WORKER) {
870 this.dispatcher_ = new SharedWorker(workerPath).port;
871 this.dispatcher_.start();
872 } else {
873 this.dispatcher_ = new Worker(workerPath);
874 }
875
876 this.dispatcher_.onmessage = this.onMessage_.bind(this);
877 this.dispatcher_.postMessage({verb: 'init'});
878
879 // Initialization is not complete until the Worker sends back the
880 // 'initialized' message. See below.
881 this.initialized_ = false;
882
883 // Map from url to callback.
884 // Note that simultaneous requests for same url are handled in MetadataCache.
885 this.callbacks_ = {};
886 }
887
888 /**
889 * Flag defining which kind of a worker to use.
890 * TODO(kaznacheev): Observe for some time and remove if SharedWorker does not
891 * cause any problems.
892 */
893 ContentProvider.USE_SHARED_WORKER = true;
894
895 ContentProvider.prototype = {
896 __proto__: MetadataProvider.prototype
897 };
898
899 /**
900 * @param {string} url The url.
901 * @return {boolean} Whether this provider supports the url.
902 */
903 ContentProvider.prototype.supportsUrl = function(url) {
904 return url.match(this.urlFilter_);
905 };
906
907 /**
908 * @param {string} type The metadata type.
909 * @return {boolean} Whether this provider provides this metadata.
910 */
911 ContentProvider.prototype.providesType = function(type) {
912 return type == 'thumbnail' || type == 'fetchedMedia' || type == 'media';
913 };
914
915 /**
916 * @return {string} Unique provider id.
917 */
918 ContentProvider.prototype.getId = function() { return 'content'; };
919
920 /**
921 * Fetches the metadata.
922 * @param {string} url File url.
923 * @param {string} type Requested metadata type.
924 * @param {function(Object)} callback Callback expects a map from metadata type
925 * to metadata value.
926 * @param {Entry=} opt_entry The file entry if present.
927 */
928 ContentProvider.prototype.fetch = function(url, type, callback, opt_entry) {
929 if (opt_entry && opt_entry.isDirectory) {
930 callback({});
931 return;
932 }
933 this.callbacks_[url] = callback;
934 this.dispatcher_.postMessage({verb: 'request', arguments: [url]});
935 };
936
937 /**
938 * Dispatch a message from a metadata reader to the appropriate on* method.
939 * @param {Object} event The event.
940 * @private
941 */
942 ContentProvider.prototype.onMessage_ = function(event) {
943 var data = event.data;
944
945 var methodName =
946 'on' + data.verb.substr(0, 1).toUpperCase() + data.verb.substr(1) + '_';
947
948 if (!(methodName in this)) {
949 console.error('Unknown message from metadata reader: ' + data.verb, data);
950 return;
951 }
952
953 this[methodName].apply(this, data.arguments);
954 };
955
956 /**
957 * @return {boolean} Whether provider is ready.
958 */
959 ContentProvider.prototype.isInitialized = function() {
960 return this.initialized_;
961 };
962
963 /**
964 * Handles the 'initialized' message from the metadata reader Worker.
965 * @param {Object} regexp Regexp of supported urls.
966 * @private
967 */
968 ContentProvider.prototype.onInitialized_ = function(regexp) {
969 this.urlFilter_ = regexp;
970
971 // Tests can monitor for this state with
972 // ExtensionTestMessageListener listener("worker-initialized");
973 // ASSERT_TRUE(listener.WaitUntilSatisfied());
974 // Automated tests need to wait for this, otherwise we crash in
975 // browser_test cleanup because the worker process still has
976 // URL requests in-flight.
977 var test = chrome.test || window.top.chrome.test;
978 test.sendMessage('worker-initialized');
979 this.initialized_ = true;
980 };
981
982 /**
983 * Converts content metadata from parsers to the internal format.
984 * @param {Object} metadata The content metadata.
985 * @param {Object=} opt_result The internal metadata object ot put result in.
986 * @return {Object!} Converted metadata.
987 */
988 ContentProvider.ConvertContentMetadata = function(metadata, opt_result) {
989 var result = opt_result || {};
990
991 if ('thumbnailURL' in metadata) {
992 metadata.thumbnailTransform = metadata.thumbnailTransform || null;
993 result.thumbnail = {
994 url: metadata.thumbnailURL,
995 transform: metadata.thumbnailTransform
996 };
997 }
998
999 for (var key in metadata) {
1000 if (metadata.hasOwnProperty(key)) {
1001 if (!('media' in result)) result.media = {};
1002 result.media[key] = metadata[key];
1003 }
1004 }
1005
1006 if ('media' in result) {
1007 result.fetchedMedia = result.media;
1008 }
1009
1010 return result;
1011 };
1012
1013 /**
1014 * Handles the 'result' message from the worker.
1015 * @param {string} url File url.
1016 * @param {Object} metadata The metadata.
1017 * @private
1018 */
1019 ContentProvider.prototype.onResult_ = function(url, metadata) {
1020 var callback = this.callbacks_[url];
1021 delete this.callbacks_[url];
1022 callback(ContentProvider.ConvertContentMetadata(metadata));
1023 };
1024
1025 /**
1026 * Handles the 'error' message from the worker.
1027 * @param {string} url File url.
1028 * @param {string} step Step failed.
1029 * @param {string} error Error description.
1030 * @param {Object?} metadata The metadata, if available.
1031 * @private
1032 */
1033 ContentProvider.prototype.onError_ = function(url, step, error, metadata) {
1034 if (MetadataCache.log) // Avoid log spam by default.
1035 console.warn('metadata: ' + url + ': ' + step + ': ' + error);
1036 metadata = metadata || {};
1037 // Prevent asking for thumbnail again.
1038 metadata.thumbnailURL = '';
1039 this.onResult_(url, metadata);
1040 };
1041
1042 /**
1043 * Handles the 'log' message from the worker.
1044 * @param {Array.<*>} arglist Log arguments.
1045 * @private
1046 */
1047 ContentProvider.prototype.onLog_ = function(arglist) {
1048 if (MetadataCache.log) // Avoid log spam by default.
1049 console.log.apply(console, ['metadata:'].concat(arglist));
1050 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698