Commit 2cb36e7c authored by yawano's avatar yawano Committed by Commit bot

Add cache size and number of prefetch tasks limitations to list thumbnail loader.

BUG=438050
TEST=out/Release/browser_tests --gtest_filter=FileManagerJsTest.ListThumbnailLoader

Review URL: https://codereview.chromium.org/884003003

Cr-Commit-Position: refs/heads/master@{#314757}
parent 4d353255
......@@ -14,9 +14,8 @@
* 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).
* * Done: Implement cache size limitation.
* * Done: Modest queueing for low priority thumbnail fetches.
* * Handle other event types of FileListModel, e.g. sort.
* * Change ThumbnailLoader to directly return dataUrl.
* * Handle file types for which generic images are used.
......@@ -66,12 +65,12 @@ function ListThumbnailLoader(
this.active_ = {};
/**
* @type {Object<string, !Object>}
* @type {LRUCache<!Object>}
* @private
*
* TODO(yawano) Add size limitation to the cache.
* TODO(yawano): After ThumbnailData class is created, type this with it.
*/
this.cache_ = {};
this.cache_ = new LRUCache(ListThumbnailLoader.CACHE_SIZE);
/**
* @type {number}
......@@ -92,7 +91,7 @@ function ListThumbnailLoader(
*/
this.cursor_ = 0;
// TODO(yawano) Handle other event types of FileListModel, e.g. sort.
// TODO(yawano): Handle other event types of FileListModel, e.g. sort.
this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
}
......@@ -104,6 +103,19 @@ ListThumbnailLoader.prototype.__proto__ = cr.EventTarget.prototype;
*/
ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS = 5;
/**
* Number of prefetch requests.
* @const {number}
*/
ListThumbnailLoader.NUM_OF_PREFETCH = 10;
/**
* Cache size. Cache size must be larger than sum of high priority range size
* and number of prefetch tasks.
* @const {number}
*/
ListThumbnailLoader.CACHE_SIZE = 100;
/**
* An event handler for splice event of data model. When list is changed, start
* to rescan items.
......@@ -111,13 +123,6 @@ ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS = 5;
* @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_ = this.beginIndex_;
this.continue_();
}
......@@ -146,7 +151,9 @@ ListThumbnailLoader.prototype.setHighPriorityRange = function(
* @return {Object} If the thumbnail is not in cache, this returns null.
*/
ListThumbnailLoader.prototype.getThumbnailFromCache = function(entry) {
return this.cache_[entry.toURL()] || null;
// Since we want to evict cache based on high priority range, we use peek here
// instead of get.
return this.cache_.peek(entry.toURL()) || null;
}
/**
......@@ -156,7 +163,8 @@ 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)) {
!(this.cursor_ < this.dataModel_.length) ||
!(this.cursor_ < this.endIndex_ + ListThumbnailLoader.NUM_OF_PREFETCH)) {
return;
}
......@@ -164,7 +172,7 @@ ListThumbnailLoader.prototype.continue_ = function() {
// If the entry is a directory, already in cache or fetching, skip it.
if (entry.isDirectory ||
this.cache_[entry.toURL()] ||
this.cache_.get(entry.toURL()) ||
this.active_[entry.toURL()]) {
this.continue_();
return;
......@@ -188,7 +196,7 @@ ListThumbnailLoader.prototype.enqueue_ = function(entry) {
task.fetch().then(function(thumbnail) {
delete this.active_[thumbnail.fileUrl];
this.cache_[thumbnail.fileUrl] = thumbnail;
this.cache_.put(thumbnail.fileUrl, thumbnail);
this.dispatchThumbnailLoaded_(thumbnail);
this.continue_();
}.bind(this));
......@@ -200,7 +208,7 @@ ListThumbnailLoader.prototype.enqueue_ = function(entry) {
* @param {Object} thumbnail Thumbnail.
*/
ListThumbnailLoader.prototype.dispatchThumbnailLoaded_ = function(thumbnail) {
// TODO(yawano) Create ThumbnailLoadedEvent class.
// TODO(yawano): Create ThumbnailLoadedEvent class.
var event = new Event('thumbnailLoaded');
event.fileUrl = thumbnail.fileUrl;
event.dataUrl = thumbnail.dataUrl;
......@@ -230,7 +238,7 @@ ListThumbnailLoader.Task = function(
/**
* Fetches thumbnail.
* TODO(yawano) Add error handling.
* TODO(yawano): Add error handling.
*
* @return {!Promise} A promise which is resolved when thumbnail is fetched.
*/
......@@ -239,7 +247,7 @@ ListThumbnailLoader.Task.prototype.fetch = function() {
this.metadataCache_.getOne(this.entry_,
'thumbnail|filesystem|external|media',
function(metadata) {
// TODO(yawano) Change ThumbnailLoader to directly return data url of
// TODO(yawano): Change ThumbnailLoader to directly return data url of
// an image.
var box = this.document_.createElement('div');
......@@ -251,7 +259,7 @@ ListThumbnailLoader.Task.prototype.fetch = function() {
ThumbnailLoader.FillMode.FIT,
ThumbnailLoader.OptimizationMode.DISCARD_DETACHED,
function(image, transform) {
// TODO(yawano) Transform an image if necessary.
// TODO(yawano): Transform an image if necessary.
var canvas = this.document_.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
......@@ -259,7 +267,7 @@ ListThumbnailLoader.Task.prototype.fetch = function() {
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
// TODO(yawano) Create ThumbnailData class.
// TODO(yawano): Create ThumbnailData class.
resolve({
fileUrl: this.entry_.toURL(),
dataUrl: canvas.toDataURL('image/jpeg', 0.5),
......
......@@ -9,6 +9,7 @@
<script src="../../../../webui/resources/js/cr/event_target.js"></script>
<script src="../../../../webui/resources/js/cr/ui.js"></script>
<script src="../../../../webui/resources/js/cr/ui/array_data_model.js"></script>
<script src="../../common/js/lru_cache.js"></script>
<script src="../../common/js/mock_entry.js"></script>
<script src="../../common/js/unittest_util.js"></script>
<script src="directory_contents.js"></script>
......
......@@ -52,9 +52,12 @@ var entry2 = new MockEntry(fileSystem, '/Test2.jpg');
var entry3 = new MockEntry(fileSystem, '/Test3.jpg');
var entry4 = new MockEntry(fileSystem, '/Test4.jpg');
var entry5 = new MockEntry(fileSystem, '/Test5.jpg');
var entry6 = new MockEntry(fileSystem, '/Test6.jpg');
function setUp() {
ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS = 2;
ListThumbnailLoader.NUM_OF_PREFETCH = 1;
ListThumbnailLoader.CACHE_SIZE = 5;
MockThumbnailLoader.setTestImageDataUrl(generateSampleImageDataUrl(document));
getOneCallbacks = {};
......@@ -81,6 +84,14 @@ function resolveGetOneCallback(url) {
delete getOneCallbacks[url];
}
function areEntriesInCache(entries) {
for (var i = 0; i < entries.length; i++) {
if (null === listThumbnailLoader.getThumbnailFromCache(entries[i]))
return false;
}
return true;
}
/**
* Story test for list thumbnail loader.
*/
......@@ -142,14 +153,6 @@ function testStory(callback) {
!!getOneCallbacks[entry4.toURL()] &&
Object.keys(getOneCallbacks).length === 2;
});
}).then(function() {
// Cache is deleted when the item is removed from the list.
var result = fileListModel.splice(2, 1); // Remove Test2.jpg.
// Fail to fetch thumbnail from cache.
return waitUntil(function() {
return listThumbnailLoader.getThumbnailFromCache(entry2) === null;
});
}), callback);
}
......@@ -167,3 +170,54 @@ function testRangeIsAtTheEndOfList() {
assertEquals('filesystem:volume-id/Test5.jpg',
Object.keys(getOneCallbacks)[0]);
}
function testCache(callback) {
ListThumbnailLoader.NUM_OF_MAX_ACTIVE_TASKS = 5;
// Set high priority range to 0 - 2.
listThumbnailLoader.setHighPriorityRange(0, 2);
fileListModel.push(entry1, entry2, entry3, entry4, entry5, entry6);
resolveGetOneCallback(entry1.toURL());
// In this test case, entry 3 is resolved earlier than entry 2.
resolveGetOneCallback(entry3.toURL());
resolveGetOneCallback(entry2.toURL());
assertEquals(0, Object.keys(getOneCallbacks).length);
reportPromise(waitUntil(function() {
return areEntriesInCache([entry3, entry2, entry1]);
}).then(function() {
// Move high priority range to 1 - 3.
listThumbnailLoader.setHighPriorityRange(1, 3);
resolveGetOneCallback(entry4.toURL());
assertEquals(0, Object.keys(getOneCallbacks).length);
return waitUntil(function() {
return areEntriesInCache([entry4, entry3, entry2, entry1]);
});
}).then(function() {
// Move high priority range to 4 - 6.
listThumbnailLoader.setHighPriorityRange(4, 6);
resolveGetOneCallback(entry5.toURL());
resolveGetOneCallback(entry6.toURL());
assertEquals(0, Object.keys(getOneCallbacks).length);
return waitUntil(function() {
return areEntriesInCache([entry6, entry5, entry4, entry3, entry2]);
});
}).then(function() {
// Move high priority range to 3 - 5.
listThumbnailLoader.setHighPriorityRange(3, 5);
assertEquals(0, Object.keys(getOneCallbacks).length);
assertTrue(areEntriesInCache([entry6, entry5, entry4, entry3, entry2]));
// Move high priority range to 0 - 2.
listThumbnailLoader.setHighPriorityRange(0, 2);
resolveGetOneCallback(entry1.toURL());
assertEquals(0, Object.keys(getOneCallbacks).length);
return waitUntil(function() {
return areEntriesInCache([entry3, entry2, entry1, entry6, entry5]);
});
}), callback);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment