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

Unified Diff: ui/file_manager/file_manager/background/js/import_history.js

Issue 677213002: Add ImportHistory class and RecordStorage class. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Nuke an obsolete comment. Created 6 years, 2 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/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_));
+};

Powered by Google App Engine
This is Rietveld 408576698