Chromium Code Reviews| Index: ui/file_manager/file_manager/background/js/import_history.js |
| diff --git a/ui/file_manager/file_manager/background/js/import_history.js b/ui/file_manager/file_manager/background/js/import_history.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6d331f957a3f3267f887bb19959f67a88b153046 |
| --- /dev/null |
| +++ b/ui/file_manager/file_manager/background/js/import_history.js |
| @@ -0,0 +1,315 @@ |
| +// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
|
mtomasz
2014/10/28 01:36:32
nit: Copyright (c) 2014 -> Copyright 2014
Steve McKay
2014/10/28 18:04:02
Done.
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +'use strict'; |
| + |
| +/** |
| + * @constructor |
| + * |
| + * @param {!RecordStorage} storage |
|
mtomasz
2014/10/27 00:40:12
nit: @param description is missing.
Steve McKay
2014/10/27 21:06:29
Descriptions are optional in google3 JS rules, esp
mtomasz
2014/10/28 01:36:32
Basically in Chromium we don't follow google3 styl
Steve McKay
2014/10/28 18:04:02
Acknowledged.
|
| + */ |
| +function ImportHistory(storage) { |
| + |
| + /** @private {!RecordStorage} */ |
|
mtomasz
2014/10/27 00:40:12
@private -> @type...\n@private
Steve McKay
2014/10/27 21:06:29
The @type is inferred, Closure totally supports th
mtomasz
2014/10/28 01:36:32
You're right. This seems to be accepted by our sty
Steve McKay
2014/10/28 18:04:02
I think you'll like it too. Closure is very verbos
|
| + this.storage_ = storage; |
| + |
| + /** @private {!Object.<string, !Array.<string>>} */ |
|
mtomasz
2014/10/27 00:40:12
ditto
Steve McKay
2014/10/27 21:06:29
Acknowledged.
|
| + this.history_ = {}; |
| +}; |
| + |
|
mtomasz
2014/10/27 00:40:12
nit: We don't really use \n\n between methods in C
Steve McKay
2014/10/27 21:06:29
Done. I've never really liked the whitespace rules
|
| + |
| +/** |
| + * Use this factory method to get a fully ready instance of ImportHistory. |
| + * |
| + * @param {!RecordStorage} storage |
| + * |
| + * @return {!Promise.<!ImportHistory>} Settles when the history object is ready. |
| + */ |
| +ImportHistory.load = function(storage) { |
| + var history = new ImportHistory(storage); |
| + return history.reload().then( |
| + function() { |
| + return history; |
| + }); |
| +}; |
| + |
| + |
| +/** |
| + * Reloads history from disk. Should be called when the file |
| + * is synced. |
| + * |
| + * @return {!Promise} Settles when history has been loaded. |
| + */ |
| +ImportHistory.prototype.reload = function() { |
| + this.history_ = {}; |
| + return this.storage_.readAll() |
| + .then( |
| + function(entries) { |
| + entries.forEach( |
| + function(entry) { |
| + this.updateHistoryRecord_(entry[0], entry[1]); |
| + }.bind(this)); |
| + }.bind(this)) |
| + .catch( |
| + function(e) { |
| + console.log('Error', JSON.stringify(e)); |
|
mtomasz
2014/10/27 00:40:12
In Files app code we use in Promise catch:
console
Steve McKay
2014/10/27 21:06:29
Ah, yes. I meant to ask about best practices in er
mtomasz
2014/10/28 01:36:32
If you find out the perfect way to handle errors i
Steve McKay
2014/10/28 18:04:02
:/
Ben's been working on a best practices doc for
|
| + }); |
| +}; |
| + |
| + |
| +/** |
| + * Adds a history entry to the in-memory history model. |
| + * @param {string} key |
|
mtomasz
2014/10/27 00:40:12
nit: Descriptions missing.
Steve McKay
2014/10/27 21:06:29
I'd say this is one of those that can be skipped--
mtomasz
2014/10/28 01:36:32
We used to write descriptions for each @param as t
fukino
2014/10/28 04:05:59
I think we can omit descriptions for each @param w
Steve McKay
2014/10/28 18:04:02
Acknowledged.
|
| + * @param {string} destination |
| + */ |
| +ImportHistory.prototype.updateHistoryRecord_ = function(key, destination) { |
| + if (key in this.history_) { |
| + this.history_[key].push(destination); |
| + } else { |
| + this.history_[key] = [destination]; |
| + } |
| +}; |
| + |
| + |
| +/** |
| + * @param {!FileEntry} entry |
| + * @param {string} destination |
| + * @return {!Promise.<boolean>} Settles with true if the FileEntry |
| + * was previously imported to the specified destination. |
| + */ |
| +ImportHistory.prototype.wasImported = function(entry, destination) { |
| + return this.createKey_(entry) |
| + .then( |
| + function(key) { |
| + return this.getDestinations_(key).indexOf(destination) >= 0; |
| + }.bind(this)) |
| + .catch( |
| + function(e) { |
|
mtomasz
2014/10/27 00:40:12
nit: e -> error.
Steve McKay
2014/10/27 21:06:29
Done.
|
| + console.log('Error', JSON.stringify(e)); |
| + }); |
| +}; |
| + |
| + |
| +/** |
| + * @param {!FileEntry} entry |
| + * @param {string} destination |
| + * @return {!Promise.<boolean>} Settles with true if the FileEntry |
| + * was previously imported to the specified destination. |
| + */ |
| +ImportHistory.prototype.markImported = function(entry, destination) { |
| + return this.createKey_(entry) |
| + .then(this.addDestination_.bind(this, destination)) |
| + .catch( |
| + function(e) { |
| + console.log('Error', JSON.stringify(e)); |
| + }); |
| +}; |
| + |
| + |
| +/** |
| + * @param {string} key |
| + * @return {!Array.<string>} The list of previously noted |
| + * destinations, or an empty array, if none. |
| + */ |
| +ImportHistory.prototype.getDestinations_ = function(key) { |
| + return key in this.history_ ? this.history_[key] : []; |
| +}; |
| + |
| + |
| +/** |
| + * @param {string} destination |
| + * @param {string} key |
| + * @return {!Promise} |
| + */ |
| +ImportHistory.prototype.addDestination_ = function(destination, key) { |
| + this.updateHistoryRecord_(key, destination); |
| + return this.storage_.write([key, destination]); |
| +}; |
| + |
| + |
| +/** |
| + * @param {!FileEntry} entry |
| + * @return {!Promise.<string>} Settles with a the key is available. |
| + */ |
| +ImportHistory.prototype.createKey_ = function(fileEntry) { |
| + var entry = new FileEntryWrapper(fileEntry); |
| + return entry.getMetadata() |
| + .then( |
| + function(metadata) { |
| + // TODO(smckay): deal with the case where metadata fields are empty. |
| + return metadata['modificationTime'] + '_' + metadata['size']; |
| + }.bind(this)); |
| +}; |
| + |
| + |
| +/** |
| + * An Object storage mechanism built on top of a FileEntry. |
| + * |
| + * @param {!FileEntry} entry |
| + * |
| + * @constructor |
| + */ |
| +function RecordStorage(entry) { |
| + /** @private {!FileEntryWrapper} */ |
| + this.entry_ = new FileEntryWrapper(entry); |
| +}; |
| + |
| + |
| +/** |
| + * Adds a new record to the end of the file. |
| + * |
| + * @param {!Object} record |
| + * @return {!Promise} Settles when the record has been added. |
| + */ |
| +RecordStorage.prototype.write = function(record) { |
| + // TODO(smckay): should we make an effort to reuse a file writer? |
| + return this.entry_.createWriter() |
| + .then(this.writeRecord_.bind(this, record)); |
| +}; |
| + |
| + |
| +/** |
| + * Appends a new record to the end of the file. |
| + * |
| + * @param {!Object} record |
| + * @param {!FileWriter} writer |
| + * @return {!Promise} Settles when the write has been completed. |
| + */ |
| +RecordStorage.prototype.writeRecord_ = function(record, writer) { |
| + return new Promise( |
| + function(resolve, reject) { |
| + var blob = new Blob( |
| + [JSON.stringify(record) + ',\n'], |
| + {type: 'application/json; charset=utf-8'}); |
| + |
| + writer.onwriteend = function() { |
| + resolve(); |
| + }; |
| + writer.onerror = function() { |
| + reject(); |
| + }; |
| + |
| + writer.seek(writer.length); |
| + writer.write(blob); |
| + }.bind(this)); |
| +}; |
| + |
| + |
| +/** |
| + * Reads all records from the entry. |
| + * |
| + * @return {!Promise.<!Array.<!Object>>} |
| + */ |
| +RecordStorage.prototype.readAll = function() { |
| + return this.entry_.file() |
| + .then(this.readFileAsText_.bind(this), function() { return '';}) |
| + .then(this.parse_.bind(this)) |
| + .then( |
| + function(entries) { |
| + return entries; |
| + }) |
| + .catch( |
| + function(e) { |
| + console.log('Error', JSON.stringify(e)); |
| + }); |
| +}; |
| + |
| + |
| +/** |
| + * Reads all lines from the entry. |
| + * |
| + * @param {!File} file |
| + * @return {!Promise.<!Array.<string>>} |
| + * @private |
| + */ |
| +RecordStorage.prototype.readFileAsText_ = function(file) { |
| + return new Promise( |
| + function(resolve, reject) { |
| + |
| + var reader = new FileReader(); |
| + |
| + reader.onloadend = function() { |
| + if (!!reader.error) { |
| + resolve(''); |
| + } else { |
| + resolve(reader.result); |
| + } |
| + }; |
| + |
| + reader.onerror = function(e) { |
| + console.log('Error', JSON.stringify(e)); |
| + reject(e); |
| + }; |
| + |
| + reader.readAsText(file); |
| + }.bind(this)); |
| +}; |
| + |
| + |
| +/** |
| + * Parses the text. |
| + * |
| + * @param {string} text |
| + * @return {!Promise.<!Array.<!Object>>} |
| + * @private |
| + */ |
| +RecordStorage.prototype.parse_ = function(text) { |
| + return new Promise( |
| + function(resolve, reject){ |
| + if (text.length == 0) { |
| + resolve([]); |
| + } else { |
| + // Dress up the contents of the file like an array, |
| + // so the JSON object can parse it using JSON.parse. |
| + // That means we need to both: |
| + // 1) Strip the trailing ',\n' from the last record |
| + // 2) Surround the whole string in brackets. |
| + // NOTE: JSON.parse is WAY faster than parsing this |
| + // ourselves in javascript. |
| + var json = '[' + text.substring(0, text.length - 2) + ']'; |
| + resolve(JSON.parse(json)); |
| + } |
| + }.bind(this)); |
| +}; |
| + |
| + |
| +/** |
| + * An Object storage mechanism built on top of a FileEntry. |
| + * |
| + * @param {!FileEntry} entry |
| + * |
| + * @constructor |
| + * @private |
| + */ |
| +function FileEntryWrapper(entry) { |
| + /** @private {!FileEntry} */ |
| + this.entry_ = entry; |
| +}; |
| + |
| + |
| +/** |
| + * A "Promisary" wrapper around entry.getWriter. |
| + * @return {!Promise.<!FileWriter>} |
| + */ |
| +FileEntryWrapper.prototype.createWriter = function() { |
| + return new Promise(this.entry_.createWriter.bind(this.entry_)); |
| +}; |
| + |
| + |
| +/** |
| + * A "Promisary" wrapper around entry.file. |
| + * @return {!Promise.<!File>} |
| + */ |
| +FileEntryWrapper.prototype.file = function() { |
| + return new Promise(this.entry_.file.bind(this.entry_)); |
| +}; |
| + |
| + |
| +/** |
| + * @return {!Promise.<!Object>} Data format not yet verified. |
| + * Either a string, or an object. |
| + */ |
| +FileEntryWrapper.prototype.getMetadata = function() { |
| + return new Promise(this.entry_.getMetadata.bind(this.entry_)); |
| +}; |