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..32d876f4e4ae73fd1bf6eea8a72233e83f814c5e |
| --- /dev/null |
| +++ b/ui/file_manager/file_manager/background/js/import_history.js |
| @@ -0,0 +1,331 @@ |
| +// Copyright 2014 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. |
| + |
| +'use strict'; |
| + |
| +/** |
| + * @constructor |
| + * |
| + * @param {!RecordStorage} storage |
| + */ |
| +function ImportHistory(storage) { |
| + |
| + /** @private {!RecordStorage} */ |
| + this.storage_ = storage; |
| + |
| + /** @private {!Object.<string, !Array.<string>>} */ |
| + this.history_ = {}; |
| +}; |
|
mtomasz
2014/10/29 01:10:41
nit: ; seems redundant here.
Steve McKay
2014/10/29 16:57:21
Done.
|
| + |
| +/** |
| + * Prints an error to the console. |
| + * |
| + * @param {!Error} error |
| + * @private |
| + */ |
| +ImportHistory.handleError_ = function(error) { |
| + console.error(error.stack || error); |
| +}; |
| + |
| +/** |
| + * Use this factory method to get a fully ready instance of ImportHistory. |
| + * |
| + * @param {!RecordStorage} storage |
| + * |
| + * @return {!Promise.<!ImportHistory>} Resolves when history instance 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} Resolves 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(ImportHistory.handleError_); |
| +}; |
| + |
| +/** |
| + * Adds a history entry to the in-memory history model. |
| + * @param {string} key |
| + * @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(ImportHistory.handleError_); |
| +}; |
| + |
| +/** |
| + * @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(ImportHistory.handleError_); |
| +}; |
| + |
| +/** |
| + * @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 new Promise( |
| + function(resolve, reject) { |
| + entry.getMetadata() |
| + .then( |
| + function(metadata) { |
| + if (!('modificationTime' in metadata)) { |
| + reject('File entry missing "modificationTime" field.'); |
| + } else if (!('size' in metadata)) { |
| + reject('File entry missing "size" field.'); |
| + } else { |
| + resolve( |
| + metadata['modificationTime'] + '_' + metadata['size']); |
| + } |
| + }.bind(this)); |
| + }.bind(this)); |
| +}; |
| + |
| +/** |
| + * An simple record storage mechanism. |
| + * |
| + * @interface |
| + */ |
| +function RecordStorage(entry) {}; |
| + |
| +/** |
| + * Adds a new record. |
| + * |
| + * @param {!Array.<*>} record |
| + * @return {!Promise} Resolves when record is added. |
| + */ |
| +RecordStorage.prototype.write; |
|
mtomasz
2014/10/29 01:10:41
Will this compile? We used to write an empty metho
Steve McKay
2014/10/29 16:57:21
This is purely in anticipation of Closure compilat
|
| + |
| +/** |
| + * Reads all records. |
| + * |
| + * @return {!Promise.<!Array.<!Array.<*>>>} |
| + */ |
| +RecordStorage.prototype.readAll; |
| + |
| +/** |
| + * A {@code RecordStore} that persists data in a {@code FileEntry}. |
| + * |
| + * @param {!FileEntry} entry |
| + * |
| + * @constructor |
| + * @implements {RecordStorage} |
| + */ |
| +function FileEntryRecordStorage(entry) { |
| + /** @private {!FileEntryWrapper} */ |
| + this.entry_ = new FileEntryWrapper(entry); |
| +}; |
| + |
| +/** |
| + * Prints an error to the console. |
| + * |
| + * @param {!Error} error |
| + * @private |
| + */ |
| +FileEntryRecordStorage.handleError_ = function(error) { |
| + console.error(error.stack || error); |
| +}; |
| + |
| +/** @override */ |
| +FileEntryRecordStorage.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)) |
| + .catch(FileEntryRecordStorage.handleError_); |
| +}; |
| + |
| +/** |
| + * Appends a new record to the end of the file. |
| + * |
| + * @param {!Object} record |
| + * @param {!FileWriter} writer |
| + * @return {!Promise} Resolves when write is complete. |
| + * @private |
| + */ |
| +FileEntryRecordStorage.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'}); |
|
mtomasz
2014/10/29 01:10:41
It's not really a valid json. How about simply pla
Steve McKay
2014/10/29 16:57:21
Done. text/plain; charset=UTF-8
|
| + |
| + writer.onwriteend = function() { |
| + resolve(); |
| + }; |
| + writer.onerror = function() { |
| + reject(); |
| + }; |
| + |
| + writer.seek(writer.length); |
| + writer.write(blob); |
| + }.bind(this)); |
| +}; |
| + |
| +/** @override */ |
| +FileEntryRecordStorage.prototype.readAll = function() { |
| + return this.entry_.file() |
| + .then(this.readFileAsText_.bind(this), function() { return '';}) |
|
mtomasz
2014/10/29 01:10:41
nit: space after ; missing.
Steve McKay
2014/10/29 16:57:21
Done.
|
| + .then(this.parse_.bind(this)) |
| + .then( |
| + function(entries) { |
| + return entries; |
| + }) |
| + .catch(FileEntryRecordStorage.handleError_); |
| +}; |
| + |
| +/** |
| + * Reads all lines from the entry. |
| + * |
| + * @param {!File} file |
| + * @return {!Promise.<!Array.<string>>} |
| + * @private |
| + */ |
| +FileEntryRecordStorage.prototype.readFileAsText_ = function(file) { |
| + return new Promise( |
| + function(resolve, reject) { |
| + |
| + var reader = new FileReader(); |
| + |
| + reader.onloadend = function() { |
| + if (!!reader.error) { |
| + FileEntryRecordStorage.handleError_(reader.error); |
| + resolve(''); |
| + } else { |
| + resolve(reader.result); |
| + } |
| + }; |
| + |
| + reader.onerror = function(error) { |
| + FileEntryRecordStorage.handleError_(error); |
| + reject(e); |
| + }; |
| + |
| + reader.readAsText(file); |
| + }.bind(this)); |
| +}; |
| + |
| +/** |
| + * Parses the text. |
| + * |
| + * @param {string} text |
| + * @return {!Promise.<!Array.<!Object>>} |
| + * @private |
| + */ |
| +FileEntryRecordStorage.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)); |
|
mtomasz
2014/10/29 01:10:41
Date objects will not deserialize properly from js
Steve McKay
2014/10/29 16:57:22
The value is just a string. There's no need to par
|
| + } |
| + }.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_)); |
| +}; |