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

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

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

Powered by Google App Engine
This is Rietveld 408576698