| 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..b25a8919257d36f803e5af48c388c5d3f5006754
|
| --- /dev/null
|
| +++ b/ui/file_manager/file_manager/background/js/import_history.js
|
| @@ -0,0 +1,339 @@
|
| +// 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_ = {};
|
| +}
|
| +
|
| +/**
|
| + * 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
|
| + * @private
|
| + */
|
| +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.
|
| + * @private
|
| + */
|
| +ImportHistory.prototype.getDestinations_ = function(key) {
|
| + return key in this.history_ ? this.history_[key] : [];
|
| +};
|
| +
|
| +/**
|
| + * @param {string} destination
|
| + * @param {string} key
|
| + * @return {!Promise}
|
| + * @private
|
| + */
|
| +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.
|
| + * @private
|
| + */
|
| +ImportHistory.prototype.createKey_ = function(fileEntry) {
|
| + var entry = new PromisaryFileEntry(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;
|
| +
|
| +/**
|
| + * 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 {!PromisaryFileEntry} */
|
| + this.entry_ = new PromisaryFileEntry(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: '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() {
|
| + FileEntryRecordStorage.handleError_(
|
| + new Error('Unable to read from history file.'));
|
| + return '';
|
| + })
|
| + .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);
|
| + reject();
|
| + } 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));
|
| + }
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * A wrapper for FileEntry that provides Promises.
|
| + *
|
| + * @param {!FileEntry} entry
|
| + *
|
| + * @constructor
|
| + */
|
| +function PromisaryFileEntry(entry) {
|
| + /** @private {!FileEntry} */
|
| + this.entry_ = entry;
|
| +}
|
| +
|
| +/**
|
| + * A "Promisary" wrapper around entry.getWriter.
|
| + * @return {!Promise.<!FileWriter>}
|
| + */
|
| +PromisaryFileEntry.prototype.createWriter = function() {
|
| + return new Promise(this.entry_.createWriter.bind(this.entry_));
|
| +};
|
| +
|
| +/**
|
| + * A "Promisary" wrapper around entry.file.
|
| + * @return {!Promise.<!File>}
|
| + */
|
| +PromisaryFileEntry.prototype.file = function() {
|
| + return new Promise(this.entry_.file.bind(this.entry_));
|
| +};
|
| +
|
| +/**
|
| + * @return {!Promise.<!Object>}
|
| + */
|
| +PromisaryFileEntry.prototype.getMetadata = function() {
|
| + return new Promise(this.entry_.getMetadata.bind(this.entry_));
|
| +};
|
|
|