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

Unified Diff: ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js

Issue 885323002: Initial implementation for ListThumbnailLoader. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase again. Created 5 years, 11 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 side-by-side diff with in-line comments
Download patch
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));
+}

Powered by Google App Engine
This is Rietveld 408576698