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.
8 * MetadataCache is a map from Entry to an object containing properties.
9 * Properties are divided by types, and all properties of one type are accessed
11 * Some of the properties:
13 * filesystem: size, modificationTime
15 * drive: pinned, present, hosted, availableOffline
16 * streaming: (no property)
18 * Following are not fetched for non-present drive files.
19 * media: artist, album, title, width, height, imageTransform, etc.
20 * thumbnail: url, transform
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.
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!");
35 * cache.set(entry, 'internal', {presence: 'deleted'});
37 * cache.clear([fileEntry1, fileEntry2], 'filesystem');
39 * // Getting fresh value.
40 * cache.clear(entry, 'thumbnail');
41 * cache.get(entry, 'thumbnail', function(thumbnail) {
42 * img.src = thumbnail.url;
45 * var cached = cache.getCached(entry, 'filesystem');
46 * var size = (cached && cached.size) || UNKNOWN_SIZE;
51 function MetadataCache() {
53 * Map from Entry (using Entry.toURL) to metadata. Metadata contains
54 * |properties| - an hierarchical object of values, and an object for each
55 * metadata provider: <prodiver-id>: {time, callbacks}
61 * List of metadata providers.
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.
79 this.currentCacheSize_ = 0;
82 * Time of first get query of the current batch. Items updated later than this
83 * will not be evicted.
86 this.lastBatchStart_ = new Date();
90 * Observer type: it will be notified if the changed Entry is exactly the same
91 * as the observed Entry.
93 MetadataCache.EXACT = 0;
96 * Observer type: it will be notified if the changed Entry is an immediate child
97 * of the observed Entry.
99 MetadataCache.CHILDREN = 1;
102 * Observer type: it will be notified if the changed Entry is a descendant of
103 * of the observer Entry.
105 MetadataCache.DESCENDANTS = 2;
108 * Margin of the cache size. This amount of caches may be kept in addition.
110 MetadataCache.EVICTION_THRESHOLD_MARGIN = 500;
113 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
114 * @return {MetadataCache!} The cache with all providers.
116 MetadataCache.createFull = function(volumeManager) {
117 var cache = new MetadataCache();
118 // DriveProvider should be prior to FileSystemProvider, because it covers
119 // FileSystemProvider for files in Drive.
120 cache.providers_.push(new DriveProvider(volumeManager));
121 cache.providers_.push(new FilesystemProvider());
122 cache.providers_.push(new ContentProvider());
127 * Clones metadata entry. Metadata entries may contain scalars, arrays,
128 * hash arrays and Date object. Other objects are not supported.
129 * @param {Object} metadata Metadata object.
130 * @return {Object} Cloned entry.
132 MetadataCache.cloneMetadata = function(metadata) {
133 if (metadata instanceof Array) {
135 for (var index = 0; index < metadata.length; index++) {
136 result[index] = MetadataCache.cloneMetadata(metadata[index]);
139 } else if (metadata instanceof Date) {
140 var result = new Date();
141 result.setTime(metadata.getTime());
143 } else if (metadata instanceof Object) { // Hash array only.
145 for (var property in metadata) {
146 if (metadata.hasOwnProperty(property))
147 result[property] = MetadataCache.cloneMetadata(metadata[property]);
156 * @return {boolean} Whether all providers are ready.
158 MetadataCache.prototype.isInitialized = function() {
159 for (var index = 0; index < this.providers_.length; index++) {
160 if (!this.providers_[index].isInitialized()) return false;
166 * Sets the size of cache. The actual cache size may be larger than the given
168 * @param {number} size The cache size to be set.
170 MetadataCache.prototype.setCacheSize = function(size) {
171 this.currentCacheSize_ = size;
173 if (this.totalCount_ > this.currentEvictionThreshold_())
178 * Returns the current threshold to evict caches. When the number of caches
179 * exceeds this, the cache should be evicted.
180 * @return {number} Threshold to evict caches.
183 MetadataCache.prototype.currentEvictionThreshold_ = function() {
184 return this.currentCacheSize_ * 2 + MetadataCache.EVICTION_THRESHOLD_MARGIN;
188 * Fetches the metadata, puts it in the cache, and passes to callback.
189 * If required metadata is already in the cache, does not fetch it again.
190 * @param {Entry|Array.<Entry>} entries The list of entries. May be just a
192 * @param {string} type The metadata type.
193 * @param {function(Object)} callback The metadata is passed to callback.
195 MetadataCache.prototype.get = function(entries, type, callback) {
196 if (!(entries instanceof Array)) {
197 this.getOne(entries, type, callback);
201 if (entries.length === 0) {
202 if (callback) callback([]);
207 var remaining = entries.length;
208 this.startBatchUpdates();
210 var onOneItem = function(index, value) {
211 result[index] = value;
213 if (remaining === 0) {
214 this.endBatchUpdates();
215 if (callback) setTimeout(callback, 0, result);
219 for (var index = 0; index < entries.length; index++) {
221 this.getOne(entries[index], type, onOneItem.bind(this, index));
226 * Fetches the metadata for one Entry. See comments to |get|.
227 * @param {Entry} entry The entry.
228 * @param {string} type Metadata type.
229 * @param {function(Object)} callback The callback.
231 MetadataCache.prototype.getOne = function(entry, type, callback) {
232 if (type.indexOf('|') !== -1) {
233 var types = type.split('|');
235 var typesLeft = types.length;
237 var onOneType = function(requestedType, metadata) {
238 result[requestedType] = metadata;
240 if (typesLeft === 0) callback(result);
243 for (var index = 0; index < types.length; index++) {
244 this.getOne(entry, types[index], onOneType.bind(null, types[index]));
249 callback = callback || function() {};
251 var entryURL = entry.toURL();
252 if (!(entryURL in this.cache_)) {
253 this.cache_[entryURL] = this.createEmptyItem_();
257 var item = this.cache_[entryURL];
259 if (type in item.properties) {
260 callback(item.properties[type]);
264 this.startBatchUpdates();
265 var providers = this.providers_.slice();
269 var onFetched = function() {
270 if (type in item.properties) {
271 self.endBatchUpdates();
272 // Got properties from provider.
273 callback(item.properties[type]);
279 var onProviderProperties = function(properties) {
280 var id = currentProvider.getId();
281 var fetchedCallbacks = item[id].callbacks;
282 delete item[id].callbacks;
283 item.time = new Date();
284 self.mergeProperties_(entry, properties);
286 for (var index = 0; index < fetchedCallbacks.length; index++) {
287 fetchedCallbacks[index]();
291 var queryProvider = function() {
292 var id = currentProvider.getId();
293 if ('callbacks' in item[id]) {
294 // We are querying this provider now.
295 item[id].callbacks.push(onFetched);
297 item[id].callbacks = [onFetched];
298 currentProvider.fetch(entry, type, onProviderProperties);
302 var tryNextProvider = function() {
303 if (providers.length === 0) {
304 self.endBatchUpdates();
305 callback(item.properties[type] || null);
309 currentProvider = providers.shift();
310 if (currentProvider.supportsEntry(entry) &&
311 currentProvider.providesType(type)) {
322 * Returns the cached metadata value, or |null| if not present.
323 * @param {Entry} entry Entry.
324 * @param {string} type The metadata type.
325 * @return {Object} The metadata or null.
327 MetadataCache.prototype.getCached = function(entry, type) {
328 // Entry.cachedUrl may be set in DirectoryContents.onNewEntries_().
329 // See the comment there for detail.
330 var entryURL = entry.cachedUrl || entry.toURL();
331 var cache = this.cache_[entryURL];
332 return cache ? (cache.properties[type] || null) : null;
336 * Puts the metadata into cache
337 * @param {Entry|Array.<Entry>} entries The list of entries. May be just a
339 * @param {string} type The metadata type.
340 * @param {Array.<Object>} values List of corresponding metadata values.
342 MetadataCache.prototype.set = function(entries, type, values) {
343 if (!(entries instanceof Array)) {
348 this.startBatchUpdates();
349 for (var index = 0; index < entries.length; index++) {
350 var entryURL = entries[index].toURL();
351 if (!(entryURL in this.cache_)) {
352 this.cache_[entryURL] = this.createEmptyItem_();
355 this.cache_[entryURL].properties[type] = values[index];
356 this.notifyObservers_(entries[index], type);
358 this.endBatchUpdates();
362 * Clears the cached metadata values.
363 * @param {Entry|Array.<Entry>} entries The list of entries. May be just a
365 * @param {string} type The metadata types or * for any type.
367 MetadataCache.prototype.clear = function(entries, type) {
368 if (!(entries instanceof Array))
371 var types = type.split('|');
373 for (var index = 0; index < entries.length; index++) {
374 var entry = entries[index];
375 var entryURL = entry.toURL();
376 if (entryURL in this.cache_) {
378 this.cache_[entryURL].properties = {};
380 for (var j = 0; j < types.length; j++) {
382 delete this.cache_[entryURL].properties[type];
390 * Clears the cached metadata values recursively.
391 * @param {Entry} entry An entry to be cleared recursively from cache.
392 * @param {string} type The metadata types or * for any type.
394 MetadataCache.prototype.clearRecursively = function(entry, type) {
395 var types = type.split('|');
396 var keys = Object.keys(this.cache_);
397 var entryURL = entry.toURL();
399 for (var index = 0; index < keys.length; index++) {
400 var cachedEntryURL = keys[index];
401 if (cachedEntryURL.substring(0, entryURL.length) === entryURL) {
403 this.cache_[cachedEntryURL].properties = {};
405 for (var j = 0; j < types.length; j++) {
407 delete this.cache_[cachedEntryURL].properties[type];
415 * Adds an observer, which will be notified when metadata changes.
416 * @param {Entry} entry The root entry to look at.
417 * @param {number} relation This defines, which items will trigger the observer.
418 * See comments to |MetadataCache.EXACT| and others.
419 * @param {string} type The metadata type.
420 * @param {function(Array.<Entry>, Object.<string, Object>)} observer Map of
421 * entries and corresponding metadata values are passed to this callback.
422 * @return {number} The observer id, which can be used to remove it.
424 MetadataCache.prototype.addObserver = function(
425 entry, relation, type, observer) {
426 var entryURL = entry.toURL();
428 if (relation === MetadataCache.CHILDREN)
429 re = entryURL + '(/[^/]*)?';
430 else if (relation === MetadataCache.DESCENDANTS)
431 re = entryURL + '(/.*)?';
435 var id = ++this.observerId_;
436 this.observers_.push({
437 re: new RegExp('^' + re + '$'),
448 * Removes the observer.
449 * @param {number} id Observer id.
450 * @return {boolean} Whether observer was removed or not.
452 MetadataCache.prototype.removeObserver = function(id) {
453 for (var index = 0; index < this.observers_.length; index++) {
454 if (this.observers_[index].id === id) {
455 this.observers_.splice(index, 1);
463 * Start batch updates.
465 MetadataCache.prototype.startBatchUpdates = function() {
467 if (this.batchCount_ === 1)
468 this.lastBatchStart_ = new Date();
472 * End batch updates. Notifies observers if all nested updates are finished.
474 MetadataCache.prototype.endBatchUpdates = function() {
476 if (this.batchCount_ !== 0) return;
477 if (this.totalCount_ > this.currentEvictionThreshold_())
479 for (var index = 0; index < this.observers_.length; index++) {
480 var observer = this.observers_[index];
483 for (var entryURL in observer.pending) {
484 if (observer.pending.hasOwnProperty(entryURL) &&
485 entryURL in this.cache_) {
486 var entry = observer.pending[entryURL];
488 properties[entryURL] =
489 this.cache_[entryURL].properties[observer.type] || null;
492 observer.pending = {};
493 if (entries.length > 0) {
494 observer.callback(entries, properties);
500 * Notifies observers or puts the data to pending list.
501 * @param {Entry} entry Changed entry.
502 * @param {string} type Metadata type.
505 MetadataCache.prototype.notifyObservers_ = function(entry, type) {
506 var entryURL = entry.toURL();
507 for (var index = 0; index < this.observers_.length; index++) {
508 var observer = this.observers_[index];
509 if (observer.type === type && observer.re.test(entryURL)) {
510 if (this.batchCount_ === 0) {
511 // Observer expects array of urls and map of properties.
513 property[entryURL] = this.cache_[entryURL].properties[type] || null;
517 observer.pending[entryURL] = entry;
524 * Removes the oldest items from the cache.
525 * This method never removes the items from last batch.
528 MetadataCache.prototype.evict_ = function() {
531 // We leave only a half of items, so we will not call evict_ soon again.
532 var desiredCount = this.currentEvictionThreshold_();
533 var removeCount = this.totalCount_ - desiredCount;
534 for (var url in this.cache_) {
535 if (this.cache_.hasOwnProperty(url) &&
536 this.cache_[url].time < this.lastBatchStart_) {
541 toRemove.sort(function(a, b) {
542 var aTime = this.cache_[a].time;
543 var bTime = this.cache_[b].time;
544 return aTime < bTime ? -1 : aTime > bTime ? 1 : 0;
547 removeCount = Math.min(removeCount, toRemove.length);
548 this.totalCount_ -= removeCount;
549 for (var index = 0; index < removeCount; index++) {
550 delete this.cache_[toRemove[index]];
555 * @return {Object} Empty cache item.
558 MetadataCache.prototype.createEmptyItem_ = function() {
559 var item = {properties: {}};
560 for (var index = 0; index < this.providers_.length; index++) {
561 item[this.providers_[index].getId()] = {};
567 * Caches all the properties from data to cache entry for the entry.
568 * @param {Entry} entry The file entry.
569 * @param {Object} data The properties.
572 MetadataCache.prototype.mergeProperties_ = function(entry, data) {
573 if (data === null) return;
574 var properties = this.cache_[entry.toURL()].properties;
575 for (var type in data) {
576 if (data.hasOwnProperty(type)) {
577 properties[type] = data[type];
578 this.notifyObservers_(entry, type);
584 * Base class for metadata providers.
587 function MetadataProvider() {
591 * @param {Entry} entry The entry.
592 * @return {boolean} Whether this provider supports the entry.
594 MetadataProvider.prototype.supportsEntry = function(entry) { return false; };
597 * @param {string} type The metadata type.
598 * @return {boolean} Whether this provider provides this metadata.
600 MetadataProvider.prototype.providesType = function(type) { return false; };
603 * @return {string} Unique provider id.
605 MetadataProvider.prototype.getId = function() { return ''; };
608 * @return {boolean} Whether provider is ready.
610 MetadataProvider.prototype.isInitialized = function() { return true; };
613 * Fetches the metadata. It's suggested to return all the metadata this provider
615 * @param {Entry} entry File entry.
616 * @param {string} type Requested metadata type.
617 * @param {function(Object)} callback Callback expects a map from metadata type
620 MetadataProvider.prototype.fetch = function(entry, type, callback) {
621 throw new Error('Default metadata provider cannot fetch.');
626 * Provider of filesystem metadata.
627 * This provider returns the following objects:
628 * filesystem: { size, modificationTime }
631 function FilesystemProvider() {
632 MetadataProvider.call(this);
635 FilesystemProvider.prototype = {
636 __proto__: MetadataProvider.prototype
640 * @param {Entry} entry The entry.
641 * @return {boolean} Whether this provider supports the entry.
643 FilesystemProvider.prototype.supportsEntry = function(entry) {
648 * @param {string} type The metadata type.
649 * @return {boolean} Whether this provider provides this metadata.
651 FilesystemProvider.prototype.providesType = function(type) {
652 return type === 'filesystem';
656 * @return {string} Unique provider id.
658 FilesystemProvider.prototype.getId = function() { return 'filesystem'; };
661 * Fetches the metadata.
662 * @param {Entry} entry File entry.
663 * @param {string} type Requested metadata type.
664 * @param {function(Object)} callback Callback expects a map from metadata type
667 FilesystemProvider.prototype.fetch = function(
668 entry, type, callback) {
669 function onError(error) {
673 function onMetadata(entry, metadata) {
676 size: (entry.isFile ? (metadata.size || 0) : -1),
677 modificationTime: metadata.modificationTime
682 entry.getMetadata(onMetadata.bind(null, entry), onError);
686 * Provider of drive metadata.
687 * This provider returns the following objects:
688 * drive: { pinned, hosted, present, customIconUrl, etc. }
689 * thumbnail: { url, transform }
691 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
694 function DriveProvider(volumeManager) {
695 MetadataProvider.call(this);
698 * @type {VolumeManagerWrapper}
701 this.volumeManager_ = volumeManager;
703 // We batch metadata fetches into single API call.
705 this.callbacks_ = [];
706 this.scheduled_ = false;
708 this.callApiBound_ = this.callApi_.bind(this);
711 DriveProvider.prototype = {
712 __proto__: MetadataProvider.prototype
716 * @param {Entry} entry The entry.
717 * @return {boolean} Whether this provider supports the entry.
719 DriveProvider.prototype.supportsEntry = function(entry) {
720 var locationInfo = this.volumeManager_.getLocationInfo(entry);
721 return locationInfo && locationInfo.isDriveBased;
725 * @param {string} type The metadata type.
726 * @return {boolean} Whether this provider provides this metadata.
728 DriveProvider.prototype.providesType = function(type) {
729 return type === 'drive' || type === 'thumbnail' ||
730 type === 'streaming' || type === 'media' || type === 'filesystem';
734 * @return {string} Unique provider id.
736 DriveProvider.prototype.getId = function() { return 'drive'; };
739 * Fetches the metadata.
740 * @param {Entry} entry File entry.
741 * @param {string} type Requested metadata type.
742 * @param {function(Object)} callback Callback expects a map from metadata type
745 DriveProvider.prototype.fetch = function(entry, type, callback) {
746 this.entries_.push(entry);
747 this.callbacks_.push(callback);
748 if (!this.scheduled_) {
749 this.scheduled_ = true;
750 setTimeout(this.callApiBound_, 0);
755 * Schedules the API call.
758 DriveProvider.prototype.callApi_ = function() {
759 this.scheduled_ = false;
761 var entries = this.entries_;
762 var callbacks = this.callbacks_;
764 this.callbacks_ = [];
767 // TODO(mtomasz): Make getDriveEntryProperties accept Entry instead of URL.
768 var entryURLs = util.entriesToURLs(entries);
769 chrome.fileBrowserPrivate.getDriveEntryProperties(
771 function(propertiesList) {
772 console.assert(propertiesList.length === callbacks.length);
773 for (var i = 0; i < callbacks.length; i++) {
774 callbacks[i](self.convert_(propertiesList[i], entries[i]));
780 * @param {DriveEntryProperties} data Drive entry properties.
781 * @param {Entry} entry File entry.
782 * @return {boolean} True if the file is available offline.
784 DriveProvider.isAvailableOffline = function(data, entry) {
791 // What's available offline? See the 'Web' column at:
792 // http://support.google.com/drive/answer/1628467
793 var subtype = FileType.getType(entry).subtype;
794 return (subtype === 'doc' ||
795 subtype === 'draw' ||
796 subtype === 'sheet' ||
797 subtype === 'slides');
801 * @param {DriveEntryProperties} data Drive entry properties.
802 * @return {boolean} True if opening the file does not require downloading it
803 * via a metered connection.
805 DriveProvider.isAvailableWhenMetered = function(data) {
806 return data.isPresent || data.isHosted;
810 * Converts API metadata to internal format.
811 * @param {Object} data Metadata from API call.
812 * @param {Entry} entry File entry.
813 * @return {Object} Metadata in internal format.
816 DriveProvider.prototype.convert_ = function(data, entry) {
819 present: data.isPresent,
820 pinned: data.isPinned,
821 hosted: data.isHosted,
822 imageWidth: data.imageWidth,
823 imageHeight: data.imageHeight,
824 imageRotation: data.imageRotation,
825 availableOffline: DriveProvider.isAvailableOffline(data, entry),
826 availableWhenMetered: DriveProvider.isAvailableWhenMetered(data),
827 customIconUrl: data.customIconUrl || '',
828 contentMimeType: data.contentMimeType || '',
829 sharedWithMe: data.sharedWithMe,
833 result.filesystem = {
834 size: (entry.isFile ? (data.fileSize || 0) : -1),
835 modificationTime: new Date(data.lastModifiedTime)
838 if ('thumbnailUrl' in data) {
840 url: data.thumbnailUrl,
843 } else if (data.isPresent) {
844 result.thumbnail = null;
846 // Block the local fetch for drive files, which require downloading.
847 result.thumbnail = {url: '', transform: null};
850 result.media = data.isPresent ? null : {};
851 // Indicate that the data is not available in local cache.
852 // It used to have a field 'url' for streaming play, but it is
853 // derprecated. See crbug.com/174560.
854 result.streaming = data.isPresent ? null : {};
861 * Provider of content metadata.
862 * This provider returns the following objects:
863 * thumbnail: { url, transform }
864 * media: { artist, album, title, width, height, imageTransform, etc. }
865 * fetchedMedia: { same fields here }
868 function ContentProvider() {
869 MetadataProvider.call(this);
871 // Pass all URLs to the metadata reader until we have a correct filter.
872 this.urlFilter_ = /.*/;
874 var dispatcher = new SharedWorker(ContentProvider.WORKER_SCRIPT).port;
875 dispatcher.onmessage = this.onMessage_.bind(this);
876 dispatcher.postMessage({verb: 'init'});
878 this.dispatcher_ = dispatcher;
880 // Initialization is not complete until the Worker sends back the
881 // 'initialized' message. See below.
882 this.initialized_ = false;
884 // Map from Entry.toURL() to callback.
885 // Note that simultaneous requests for same url are handled in MetadataCache.
886 this.callbacks_ = {};
890 * Path of a worker script.
894 ContentProvider.WORKER_SCRIPT =
895 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/' +
896 'foreground/js/metadata/metadata_dispatcher.js';
898 ContentProvider.prototype = {
899 __proto__: MetadataProvider.prototype
903 * @param {Entry} entry The entry.
904 * @return {boolean} Whether this provider supports the entry.
906 ContentProvider.prototype.supportsEntry = function(entry) {
907 return entry.toURL().match(this.urlFilter_);
911 * @param {string} type The metadata type.
912 * @return {boolean} Whether this provider provides this metadata.
914 ContentProvider.prototype.providesType = function(type) {
915 return type === 'thumbnail' || type === 'fetchedMedia' || type === 'media';
919 * @return {string} Unique provider id.
921 ContentProvider.prototype.getId = function() { return 'content'; };
924 * Fetches the metadata.
925 * @param {Entry} entry File entry.
926 * @param {string} type Requested metadata type.
927 * @param {function(Object)} callback Callback expects a map from metadata type
930 ContentProvider.prototype.fetch = function(entry, type, callback) {
931 if (entry.isDirectory) {
935 var entryURL = entry.toURL();
936 this.callbacks_[entryURL] = callback;
937 this.dispatcher_.postMessage({verb: 'request', arguments: [entryURL]});
941 * Dispatch a message from a metadata reader to the appropriate on* method.
942 * @param {Object} event The event.
945 ContentProvider.prototype.onMessage_ = function(event) {
946 var data = event.data;
949 'on' + data.verb.substr(0, 1).toUpperCase() + data.verb.substr(1) + '_';
951 if (!(methodName in this)) {
952 console.error('Unknown message from metadata reader: ' + data.verb, data);
956 this[methodName].apply(this, data.arguments);
960 * @return {boolean} Whether provider is ready.
962 ContentProvider.prototype.isInitialized = function() {
963 return this.initialized_;
967 * Handles the 'initialized' message from the metadata reader Worker.
968 * @param {Object} regexp Regexp of supported urls.
971 ContentProvider.prototype.onInitialized_ = function(regexp) {
972 this.urlFilter_ = regexp;
974 // Tests can monitor for this state with
975 // ExtensionTestMessageListener listener("worker-initialized");
976 // ASSERT_TRUE(listener.WaitUntilSatisfied());
977 // Automated tests need to wait for this, otherwise we crash in
978 // browser_test cleanup because the worker process still has
979 // URL requests in-flight.
980 util.testSendMessage('worker-initialized');
981 this.initialized_ = true;
985 * Converts content metadata from parsers to the internal format.
986 * @param {Object} metadata The content metadata.
987 * @param {Object=} opt_result The internal metadata object ot put result in.
988 * @return {Object!} Converted metadata.
990 ContentProvider.ConvertContentMetadata = function(metadata, opt_result) {
991 var result = opt_result || {};
993 if ('thumbnailURL' in metadata) {
994 metadata.thumbnailTransform = metadata.thumbnailTransform || null;
996 url: metadata.thumbnailURL,
997 transform: metadata.thumbnailTransform
1001 for (var key in metadata) {
1002 if (metadata.hasOwnProperty(key)) {
1003 if (!('media' in result)) result.media = {};
1004 result.media[key] = metadata[key];
1008 if ('media' in result) {
1009 result.fetchedMedia = result.media;
1016 * Handles the 'result' message from the worker.
1017 * @param {string} url File url.
1018 * @param {Object} metadata The metadata.
1021 ContentProvider.prototype.onResult_ = function(url, metadata) {
1022 var callback = this.callbacks_[url];
1023 delete this.callbacks_[url];
1024 callback(ContentProvider.ConvertContentMetadata(metadata));
1028 * Handles the 'error' message from the worker.
1029 * @param {string} url File entry.
1030 * @param {string} step Step failed.
1031 * @param {string} error Error description.
1032 * @param {Object?} metadata The metadata, if available.
1035 ContentProvider.prototype.onError_ = function(url, step, error, metadata) {
1036 if (MetadataCache.log) // Avoid log spam by default.
1037 console.warn('metadata: ' + url + ': ' + step + ': ' + error);
1038 metadata = metadata || {};
1039 // Prevent asking for thumbnail again.
1040 metadata.thumbnailURL = '';
1041 this.onResult_(url, metadata);
1045 * Handles the 'log' message from the worker.
1046 * @param {Array.<*>} arglist Log arguments.
1049 ContentProvider.prototype.onLog_ = function(arglist) {
1050 if (MetadataCache.log) // Avoid log spam by default.
1051 console.log.apply(console, ['metadata:'].concat(arglist));