Index: ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js |
diff --git a/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..517f65b4409d65ccbab8d08f32e3f6e4d9a8c5ba |
--- /dev/null |
+++ b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js |
@@ -0,0 +1,275 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * A thumbnail loader for list style UI. |
+ * |
+ * ListThumbnailLoader is a thubmanil loader designed for list style ui. List |
+ * thumbnail loader loads thumbnail in a viewport of the UI. ListThumbnailLoader |
+ * is responsible to return dataUrls of valid thumbnails and fetch them with |
+ * proper priority. |
+ * |
+ * TODOs |
+ * The following list is a todo list for this class. This list will be deleted |
+ * after all of them are implemented. |
+ * * Done: Fetch thumbnails with range based priority control. |
+ * * Implement cache size limitation. |
+ * * Modest queueing for low priority thumbnail fetches (i.e. not to use up IO |
+ * by low priority tasks). |
+ * * Handle other event types of FileListModel, e.g. sort. |
+ * * Change ThumbnailLoader to directly return dataUrl. |
+ * * Handle file types for which generic images are used. |
+ * |
+ * @param {!FileListModel} dataModel A file list model. |
+ * @param {!MetadataCache} metadataCache Metadata cache. |
+ * @param {!Document} document Document. |
+ * @param {Function=} opt_thumbnailLoaderConstructor A constructor of thumbnail |
+ * loader. This argument is used for testing. |
+ * @struct |
+ * @constructor |
+ * @extends {cr.EventTarget} |
+ * @suppress {checkStructDictInheritance} |
+ */ |
+function ListThumbnailLoader( |
+ dataModel, metadataCache, document, opt_thumbnailLoaderConstructor) { |
+ /** |
+ * @type {!FileListModel} |
+ * @private |
+ */ |
+ this.dataModel_ = dataModel; |
+ |
+ /** |
+ * @type {!MetadataCache} |
+ * @private |
+ */ |
+ this.metadataCache_ = metadataCache; |
+ |
+ /** |
+ * @type {!Document} |
+ * @private |
+ */ |
+ this.document_ = document; |
+ |
+ /** |
+ * Constructor of thumbnail loader. |
+ * @type {!Function} |
+ * @private |
+ */ |
+ this.thumbnailLoaderConstructor_ = |
+ opt_thumbnailLoaderConstructor || ThumbnailLoader; |
+ |
+ /** |
+ * @type {Object<string, !ListThumbnailLoader.Task>} |
+ * @private |
+ */ |
+ this.active_ = {}; |
+ |
+ /** |
+ * @type {Object<string, !Object>} |
+ * @private |
+ * |
+ * TODO(yawano) Add size limitation to the cache. |
+ */ |
+ this.cache_ = {}; |
+ |
+ /** |
+ * @type {number} |
+ * @private |
+ */ |
+ this.beginIndex_ = 0; |
+ |
+ /** |
+ * @type {number} |
+ * @private |
+ */ |
+ this.endIndex_ = 0; |
+ |
+ /** |
+ * Cursor begins from 0, and the origin in the list is beginIndex_. |
+ * @type {number} |
+ * @private |
+ */ |
+ this.cursor_ = 0; |
+ |
+ // TODO(yawano) Handle other event types of FileListModel, e.g. sort. |
+ this.dataModel_.addEventListener('splice', this.onSplice_.bind(this)); |
+} |
+ |
+ListThumbnailLoader.prototype.__proto__ = cr.EventTarget.prototype; |
+ |
+/** |
+ * Number of maximum active tasks. |
+ * @const {number} |
+ */ |
+ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS = 5; |
+ |
+/** |
+ * An event handler for splice event of data model. When list is changed, start |
+ * to rescan items. |
+ * |
+ * @param {!Event} event Event |
+ */ |
+ListThumbnailLoader.prototype.onSplice_ = function(event) { |
+ // Delete thumbnails of removed items from cache. |
+ for (var i = 0; i < event.removed.length; i++) { |
+ var removedItem = event.removed[i]; |
+ if (this.cache_[removedItem.toURL()]) |
+ delete this.cache_[removedItem.toURL()]; |
+ } |
+ |
+ this.cursor_ = 0; |
+ this.continue_(); |
+} |
+ |
+/** |
+ * Sets high priority range in the list. |
+ * |
+ * @param {number} beginIndex Begin index of the range, inclusive. |
+ * @param {number} endIndex End index of the range, exclusive. |
+ */ |
+ListThumbnailLoader.prototype.setHighPriorityRange = function( |
+ beginIndex, endIndex) { |
+ if (!(beginIndex < endIndex)) |
+ return; |
+ |
+ this.beginIndex_ = beginIndex; |
+ this.endIndex_ = endIndex; |
+ this.cursor_ = 0; |
+ |
+ this.continue_(); |
+} |
+ |
+/** |
+ * Returns a thumbnail of an entry if it is in cache. |
+ * |
+ * @return {!Object} If the thumbnail is not in cache, this returns null. |
+ */ |
+ListThumbnailLoader.prototype.getThumbnailFromCache = function(entry) { |
+ return this.cache_[entry.toURL()] || null; |
+} |
+ |
+/** |
+ * Enqueues tasks if available. |
+ */ |
+ListThumbnailLoader.prototype.continue_ = function() { |
+ // If tasks are running full or all items are scanned, do nothing. |
+ if (!(Object.keys(this.active_).length < |
+ ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS) || |
+ !(this.cursor_ < this.dataModel_.length)) { |
+ return; |
+ } |
+ |
+ var index = (this.beginIndex_ + this.cursor_) % this.dataModel_.length; |
+ this.cursor_ += 1; |
+ |
+ var entry = /** @type {Entry} */ (this.dataModel_.item(index)); |
+ |
+ // If the entry is a directory, already in cache or fetching, skip it. |
+ if (entry.isDirectory || |
+ this.cache_[entry.toURL()] || |
+ this.active_[entry.toURL()]) { |
+ this.continue_(); |
+ return; |
+ } |
+ |
+ this.enqueue_(entry); |
+ this.continue_(); |
+} |
+ |
+/** |
+ * Enqueues a thumbnail fetch task for an entry. |
+ * |
+ * @param {!Entry} entry An entry. |
+ */ |
+ListThumbnailLoader.prototype.enqueue_ = function(entry) { |
+ var task = new ListThumbnailLoader.Task( |
+ entry, this.metadataCache_, this.document_, |
+ this.thumbnailLoaderConstructor_); |
+ |
+ this.active_[entry.toURL()] = task; |
+ |
+ task.fetch().then(function(thumbnail) { |
+ delete this.active_[thumbnail.fileUrl]; |
+ this.cache_[thumbnail.fileUrl] = thumbnail; |
+ this.dispatchThumbnailLoaded_(thumbnail); |
+ this.continue_(); |
+ }.bind(this)); |
+} |
+ |
+/** |
+ * Dispatches thumbnail loaded event. |
+ * |
+ * @param {Object} thumbnail Thumbnail. |
+ */ |
+ListThumbnailLoader.prototype.dispatchThumbnailLoaded_ = function(thumbnail) { |
+ // TODO(yawano) Create ThumbnailLoadedEvent class. |
+ var event = new Event('thumbnailLoaded'); |
+ event.fileUrl = thumbnail.fileUrl; |
+ event.dataUrl = thumbnail.dataUrl; |
+ event.width = thumbnail.width; |
+ event.height = thumbnail.height; |
+ this.dispatchEvent(event); |
+}; |
+ |
+/** |
+ * A task to load thumbnail. |
+ * |
+ * @param {!Entry} entry An entry. |
+ * @param {!MetadataCache} metadataCache Metadata cache. |
+ * @param {!Document} document Document. |
+ * @param {!Function} thumbnailLoaderConstructor A constructor of thumbnail |
+ * loader. |
+ * @constructor |
+ * @struct |
+ */ |
+ListThumbnailLoader.Task = function( |
+ entry, metadataCache, document, thumbnailLoaderConstructor) { |
+ this.entry_ = entry; |
+ this.metadataCache_ = metadataCache; |
+ this.document_ = document; |
+ this.thumbnailLoaderConstructor_ = thumbnailLoaderConstructor; |
+} |
+ |
+/** |
+ * Fetches thumbnail. |
+ * TODO(yawano) Add error handling. |
+ * |
+ * @return {!Promise} A promise which is resolved when thumbnail is fetched. |
+ */ |
+ListThumbnailLoader.Task.prototype.fetch = function() { |
+ return new Promise(function(resolve, reject) { |
+ this.metadataCache_.getOne(this.entry_, |
+ 'thumbnail|filesystem|external|media', |
+ function(metadata) { |
+ // TODO(yawano) Change ThumbnailLoader to directly return data url of |
+ // an image. |
+ var box = this.document_.createElement('div'); |
+ |
+ var thumbnailLoader = new this.thumbnailLoaderConstructor_( |
+ this.entry_, |
+ ThumbnailLoader.LoaderType.IMAGE, |
+ metadata); |
+ thumbnailLoader.load(box, |
+ ThumbnailLoader.FillMode.FIT, |
+ ThumbnailLoader.OptimizationMode.DISCARD_DETACHED, |
+ function(image, transform) { |
+ // TODO(yawano) Transform an image if necessary. |
+ var canvas = this.document_.createElement('canvas'); |
+ canvas.width = image.width; |
+ canvas.height = image.height; |
+ |
+ var context = canvas.getContext('2d'); |
+ context.drawImage(image, 0, 0); |
+ |
+ // TODO(yawano) Create ThumbnailData class. |
+ resolve({ |
+ fileUrl: this.entry_.toURL(), |
+ dataUrl: canvas.toDataURL('image/jpeg', 0.5), |
+ width: image.width, |
+ height: image.height |
+ }); |
+ }.bind(this)); |
+ }.bind(this)); |
+ }.bind(this)); |
+} |