Commit 45e673e6 authored by smckay's avatar smckay Committed by Commit bot

Show a little circly/arrowey badge for files that have been copied locally.

Update import history code to support tracking locally copied files (and their destinations). Tracking destination is necessary as we'll eventually need to do lookups against destinations in order to mark items as imported when they are fully synced to the cloud.

BUG=420680
TEST=browser_test: FileManagerJsTest.*

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

Cr-Commit-Position: refs/heads/master@{#310189}
parent 64c52354
...@@ -12,6 +12,14 @@ var importer = importer || {}; ...@@ -12,6 +12,14 @@ var importer = importer || {};
*/ */
importer.ImportHistory = function() {}; importer.ImportHistory = function() {};
/**
* @param {!FileEntry} entry
* @param {!importer.Destination} destination
* @return {!Promise.<boolean>} Resolves with true if the FileEntry
* was previously copied to the device.
*/
importer.ImportHistory.prototype.wasCopied;
/** /**
* @param {!FileEntry} entry * @param {!FileEntry} entry
* @param {!importer.Destination} destination * @param {!importer.Destination} destination
...@@ -20,6 +28,13 @@ importer.ImportHistory = function() {}; ...@@ -20,6 +28,13 @@ importer.ImportHistory = function() {};
*/ */
importer.ImportHistory.prototype.wasImported; importer.ImportHistory.prototype.wasImported;
/**
* @param {!FileEntry} entry
* @param {!importer.Destination} destination
* @param {string} url
*/
importer.ImportHistory.prototype.markCopied;
/** /**
* @param {!FileEntry} entry * @param {!FileEntry} entry
* @param {!importer.Destination} destination * @param {!importer.Destination} destination
...@@ -43,6 +58,7 @@ importer.ImportHistory.prototype.removeObserver; ...@@ -43,6 +58,7 @@ importer.ImportHistory.prototype.removeObserver;
/** @enum {string} */ /** @enum {string} */
importer.ImportHistory.State = { importer.ImportHistory.State = {
'COPIED': 'copied',
'IMPORTED': 'imported' 'IMPORTED': 'imported'
}; };
...@@ -77,12 +93,24 @@ importer.DummyImportHistory.prototype.getHistory = function() { ...@@ -77,12 +93,24 @@ importer.DummyImportHistory.prototype.getHistory = function() {
return Promise.resolve(this); return Promise.resolve(this);
}; };
/** @override */
importer.DummyImportHistory.prototype.wasCopied =
function(entry, destination) {
return Promise.resolve(this.answer_);
};
/** @override */ /** @override */
importer.DummyImportHistory.prototype.wasImported = importer.DummyImportHistory.prototype.wasImported =
function(entry, destination) { function(entry, destination) {
return Promise.resolve(this.answer_); return Promise.resolve(this.answer_);
}; };
/** @override */
importer.DummyImportHistory.prototype.markCopied =
function(entry, destination, url) {
return Promise.resolve();
};
/** @override */ /** @override */
importer.DummyImportHistory.prototype.markImported = importer.DummyImportHistory.prototype.markImported =
function(entry, destination) { function(entry, destination) {
...@@ -95,6 +123,14 @@ importer.DummyImportHistory.prototype.addObserver = function(observer) {}; ...@@ -95,6 +123,14 @@ importer.DummyImportHistory.prototype.addObserver = function(observer) {};
/** @override */ /** @override */
importer.DummyImportHistory.prototype.removeObserver = function(observer) {}; importer.DummyImportHistory.prototype.removeObserver = function(observer) {};
/**
* @private @enum {number}
*/
importer.RecordType_ = {
COPY: 0,
IMPORT: 1
};
/** /**
* An {@code ImportHistory} implementation that reads from and * An {@code ImportHistory} implementation that reads from and
* writes to a storage object. * writes to a storage object.
...@@ -110,13 +146,21 @@ importer.PersistentImportHistory = function(storage) { ...@@ -110,13 +146,21 @@ importer.PersistentImportHistory = function(storage) {
/** @private {!importer.RecordStorage} */ /** @private {!importer.RecordStorage} */
this.storage_ = storage; this.storage_ = storage;
/**
* An in-memory representation of local copy history.
* The first value is the "key" (as generated internally
* from a file entry).
* @private {!Object.<string, !Object.<!importer.Destination, string>>}
*/
this.copiedEntries_ = {};
/** /**
* An in-memory representation of import history. * An in-memory representation of import history.
* The first value is the "key" (as generated internally * The first value is the "key" (as generated internally
* from a file entry). * from a file entry).
* @private {!Object.<string, !Array.<importer.Destination>>} * @private {!Object.<string, !Array.<importer.Destination>>}
*/ */
this.entries_ = {}; this.importedEntries_ = {};
/** @private {!Array.<!importer.ImportHistory.Observer>} */ /** @private {!Array.<!importer.ImportHistory.Observer>} */
this.observers_ = []; this.observers_ = [];
...@@ -135,11 +179,15 @@ importer.PersistentImportHistory = function(storage) { ...@@ -135,11 +179,15 @@ importer.PersistentImportHistory = function(storage) {
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.refresh_ = function() { importer.PersistentImportHistory.prototype.refresh_ = function() {
var oldEntries = this.entries_; var oldCopiedEntries = this.copiedEntries_;
this.entries_ = {}; var oldImportedEntries = this.importedEntries_;
this.copiedEntries_ = {};
this.importedEntries_ = {};
return this.storage_.readAll() return this.storage_.readAll()
.then(this.updateHistoryRecords_.bind(this)) .then(this.updateHistoryRecords_.bind(this))
.then(this.mergeEntries_.bind(this, oldEntries)) .then(this.mergeCopiedEntries_.bind(this, oldCopiedEntries))
.then(this.mergeImportedEntries_.bind(this, oldImportedEntries))
.then( .then(
/** /**
* @return {!importer.PersistentImportHistory} * @return {!importer.PersistentImportHistory}
...@@ -151,32 +199,58 @@ importer.PersistentImportHistory.prototype.refresh_ = function() { ...@@ -151,32 +199,58 @@ importer.PersistentImportHistory.prototype.refresh_ = function() {
}; };
/** /**
* Adds all entries not already present in history. * Adds all copied entries into existing entries.
*
* @param {!Object.<string, !Object.<!importer.Destination, string>>} entries
* @return {!Promise.<?>} Resolves once all updates are completed.
* @private
*/
importer.PersistentImportHistory.prototype.mergeCopiedEntries_ =
function(entries) {
var promises = [];
for (var key in entries) {
for (var destination in entries[key]) {
// This method is only called when data is reloaded from disk.
// In such a situation we defend against loss of in-memory data
// by copying it out of the way, reloading data from disk, then
// merging that data back into the freshly loaded data. For that
// reason we will write newly created entries back to disk.
var path = entries[key][destination];
if (this.updateInMemoryRecord_(
importer.RecordType_.COPY, key, destination, path)) {
promises.push(this.storage_.write(
[importer.RecordType_.COPY, key, destination, path]));
}
}
}
return Promise.all(promises);
};
/**
* Adds all imported entries into existing entries.
* *
* @param {!Object.<string, !Array.<string>>} entries * @param {!Object.<string, !Array.<string>>} entries
* @return {!Promise.<?>} Resolves once all updates are completed. * @return {!Promise.<?>} Resolves once all updates are completed.
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.mergeEntries_ = function(entries) { importer.PersistentImportHistory.prototype.mergeImportedEntries_ =
function(entries) {
var promises = []; var promises = [];
Object.keys(entries).forEach( for (var key in entries) {
/** entries[key].forEach(
* @param {string} key /**
* @this {importer.PersistentImportHistory} * @param {string} key
*/ * @this {importer.PersistentImportHistory}
function(key) { */
entries[key].forEach( function(destination) {
/** if (this.updateInMemoryRecord_(
* @param {string} key importer.RecordType_.IMPORT, key, destination)) {
* @this {importer.PersistentImportHistory} promises.push(this.storage_.write(
*/ [importer.RecordType_.IMPORT, key, destination]));
function(destination) { }
if (this.getDestinations_(key).indexOf(destination) >= 0) {
this.updateHistoryRecord_(key, destination);
promises.push(this.storage_.write([key, destination]));
}
}.bind(this)); }.bind(this));
}.bind(this)); }
return Promise.all(promises); return Promise.all(promises);
}; };
...@@ -212,25 +286,65 @@ importer.PersistentImportHistory.prototype.updateHistoryRecords_ = ...@@ -212,25 +286,65 @@ importer.PersistentImportHistory.prototype.updateHistoryRecords_ =
* @this {importer.PersistentImportHistory} * @this {importer.PersistentImportHistory}
*/ */
function(record) { function(record) {
this.updateHistoryRecord_(record[0], record[1]); this.updateInMemoryRecord_(record[0], record[1], record[2], record[3]);
}.bind(this)); }.bind(this));
}; };
/** /**
* Adds a history entry to the in-memory history model. * Adds a history entry to the in-memory history model.
* @param {!importer.RecordType_} type
* @param {string} key * @param {string} key
* @param {string} destination * @param {!importer.Destination} destination
* @param {*=} opt_payload
* @return {boolean} True if a record was created.
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.updateHistoryRecord_ = importer.PersistentImportHistory.prototype.updateInMemoryRecord_ =
function(key, destination) { function(type, key, destination, opt_payload) {
if (key in this.entries_) {
this.entries_[key].push(destination); switch (type) {
} else { case importer.RecordType_.COPY:
this.entries_[key] = [destination]; if (!this.copiedEntries_.hasOwnProperty(key)) {
this.copiedEntries_[key] = {};
}
if (!this.copiedEntries_[key].hasOwnProperty(destination)) {
this.copiedEntries_[key][destination] = /** @type {string} */ (
opt_payload);
return true;
}
break;
case importer.RecordType_.IMPORT:
if (!this.importedEntries_.hasOwnProperty(key)) {
this.importedEntries_[key] = [destination];
}
if (this.importedEntries_[key].indexOf(destination) === -1) {
this.importedEntries_[key].push(destination);
return true;
}
break;
default:
console.log('Ignoring record with unrecognized type: ' + type);
return false;
} }
}; };
/** @override */
importer.PersistentImportHistory.prototype.wasCopied =
function(entry, destination) {
return this.whenReady_
.then(this.createKey_.bind(this, entry))
.then(
/**
* @param {string} key
* @return {boolean}
* @this {importer.PersistentImportHistory}
*/
function(key) {
return key in this.copiedEntries_ &&
destination in this.copiedEntries_[key];
}.bind(this));
};
/** @override */ /** @override */
importer.PersistentImportHistory.prototype.wasImported = importer.PersistentImportHistory.prototype.wasImported =
function(entry, destination) { function(entry, destination) {
...@@ -239,7 +353,7 @@ importer.PersistentImportHistory.prototype.wasImported = ...@@ -239,7 +353,7 @@ importer.PersistentImportHistory.prototype.wasImported =
.then( .then(
/** /**
* @param {string} key * @param {string} key
* @return {!Promise.<boolean>} * @return {boolean}
* @this {importer.PersistentImportHistory} * @this {importer.PersistentImportHistory}
*/ */
function(key) { function(key) {
...@@ -247,6 +361,30 @@ importer.PersistentImportHistory.prototype.wasImported = ...@@ -247,6 +361,30 @@ importer.PersistentImportHistory.prototype.wasImported =
}.bind(this)); }.bind(this));
}; };
/** @override */
importer.PersistentImportHistory.prototype.markCopied =
function(entry, destination, url) {
return this.whenReady_
.then(this.createKey_.bind(this, entry))
.then(
/**
* @param {string} key
* @return {!Promise.<?>}
* @this {importer.ImportHistory}
*/
function(key) {
return this.storeRecord_(
importer.RecordType_.COPY,
key,
destination,
url);
}.bind(this))
.then(this.notifyObservers_.bind(
this,
entry,
importer.ImportHistory.State.COPIED));
};
/** @override */ /** @override */
importer.PersistentImportHistory.prototype.markImported = importer.PersistentImportHistory.prototype.markImported =
function(entry, destination) { function(entry, destination) {
...@@ -259,9 +397,15 @@ importer.PersistentImportHistory.prototype.markImported = ...@@ -259,9 +397,15 @@ importer.PersistentImportHistory.prototype.markImported =
* @this {importer.ImportHistory} * @this {importer.ImportHistory}
*/ */
function(key) { function(key) {
return this.addDestination_(destination, key); return this.storeRecord_(
importer.RecordType_.IMPORT,
key,
destination);
}.bind(this)) }.bind(this))
.then(this.notifyObservers_.bind(this, entry)); .then(this.notifyObservers_.bind(
this,
entry,
importer.ImportHistory.State.IMPORTED));
}; };
/** @override */ /** @override */
...@@ -283,29 +427,34 @@ importer.PersistentImportHistory.prototype.removeObserver = ...@@ -283,29 +427,34 @@ importer.PersistentImportHistory.prototype.removeObserver =
/** /**
* @param {!FileEntry} subject * @param {!FileEntry} subject
* @param {!importer.ImportHistory.State} newState
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.notifyObservers_ = importer.PersistentImportHistory.prototype.notifyObservers_ =
function(subject) { function(subject, newState) {
this.observers_.forEach( this.observers_.forEach(
function(observer) { function(observer) {
observer({ observer({
state: importer.ImportHistory.State.IMPORTED, state: newState,
entry: subject entry: subject
}); });
}.bind(this)); }.bind(this));
}; };
/** /**
* @param {string} destination * @param {!importer.RecordType_} type
* @param {string} key * @param {string} key
* @param {!importer.Destination} destination
* @param {*=} opt_payload
* @return {!Promise.<?>} Resolves once the write has been completed. * @return {!Promise.<?>} Resolves once the write has been completed.
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.addDestination_ = importer.PersistentImportHistory.prototype.storeRecord_ =
function(destination, key) { function(type, key, destination, opt_payload) {
this.updateHistoryRecord_(key, destination); this.updateInMemoryRecord_(type, key, destination, opt_payload);
return this.storage_.write([key, destination]); return !!opt_payload ?
this.storage_.write([type, key, destination, opt_payload]) :
this.storage_.write([type, key, destination]);
}; };
/** /**
...@@ -315,7 +464,7 @@ importer.PersistentImportHistory.prototype.addDestination_ = ...@@ -315,7 +464,7 @@ importer.PersistentImportHistory.prototype.addDestination_ =
* @private * @private
*/ */
importer.PersistentImportHistory.prototype.getDestinations_ = function(key) { importer.PersistentImportHistory.prototype.getDestinations_ = function(key) {
return key in this.entries_ ? this.entries_[key] : []; return key in this.importedEntries_ ? this.importedEntries_[key] : [];
}; };
/** /**
...@@ -476,7 +625,7 @@ importer.ChromeSyncFileEntryProvider = function() { ...@@ -476,7 +625,7 @@ importer.ChromeSyncFileEntryProvider = function() {
}; };
/** @private @const {string} */ /** @private @const {string} */
importer.ChromeSyncFileEntryProvider.FILE_NAME_ = 'import-history.data'; importer.ChromeSyncFileEntryProvider.FILE_NAME_ = 'import-history.log';
/** /**
* Wraps chrome.syncFileSystem.onFileStatusChanged * Wraps chrome.syncFileSystem.onFileStatusChanged
...@@ -678,6 +827,9 @@ importer.FileEntryRecordStorage.prototype.write = function(record) { ...@@ -678,6 +827,9 @@ importer.FileEntryRecordStorage.prototype.write = function(record) {
*/ */
importer.FileEntryRecordStorage.prototype.writeRecord_ = importer.FileEntryRecordStorage.prototype.writeRecord_ =
function(record, writer) { function(record, writer) {
var blob = new Blob(
[JSON.stringify(record) + ',\n'],
{type: 'text/plain; charset=UTF-8'});
return new Promise( return new Promise(
/** /**
* @param {function()} resolve * @param {function()} resolve
...@@ -685,10 +837,6 @@ importer.FileEntryRecordStorage.prototype.writeRecord_ = ...@@ -685,10 +837,6 @@ importer.FileEntryRecordStorage.prototype.writeRecord_ =
* @this {importer.FileEntryRecordStorage} * @this {importer.FileEntryRecordStorage}
*/ */
function(resolve, reject) { function(resolve, reject) {
var blob = new Blob(
[JSON.stringify(record) + ',\n'],
{type: 'text/plain; charset=UTF-8'});
writer.onwriteend = resolve; writer.onwriteend = resolve;
writer.onerror = reject; writer.onerror = reject;
...@@ -759,31 +907,23 @@ importer.FileEntryRecordStorage.prototype.readFileAsText_ = function(file) { ...@@ -759,31 +907,23 @@ importer.FileEntryRecordStorage.prototype.readFileAsText_ = function(file) {
* Parses the text. * Parses the text.
* *
* @param {string} text * @param {string} text
* @return {!Promise.<!Array.<!Array.<*>>>} * @return {!Array.<!Array.<*>>}
* @private * @private
*/ */
importer.FileEntryRecordStorage.prototype.parse_ = function(text) { importer.FileEntryRecordStorage.prototype.parse_ = function(text) {
return new Promise( if (text.length === 0) {
/** return [];
* @param {function()} resolve } else {
* @param {function()} reject // Dress up the contents of the file like an array,
* @this {importer.FileEntryRecordStorage} // so the JSON object can parse it using JSON.parse.
*/ // That means we need to both:
function(resolve, reject) { // 1) Strip the trailing ',\n' from the last record
if (text.length === 0) { // 2) Surround the whole string in brackets.
resolve([]); // NOTE: JSON.parse is WAY faster than parsing this
} else { // ourselves in javascript.
// Dress up the contents of the file like an array, var json = '[' + text.substring(0, text.length - 2) + ']';
// so the JSON object can parse it using JSON.parse. return /** @type {!Array.<!Array.<*>>} */ (JSON.parse(json));
// 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));
}; };
/** /**
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<html> <html>
<body> <body>
<script src="../../common/js/volume_manager_common.js"></script>
<script src="../../common/js/importer_common.js"></script> <script src="../../common/js/importer_common.js"></script>
<script src="../../common/js/mock_entry.js"></script> <script src="../../common/js/mock_entry.js"></script>
<script src="../../common/js/unittest_util.js"></script> <script src="../../common/js/unittest_util.js"></script>
......
...@@ -12,7 +12,10 @@ var FILE_SIZE = 1234; ...@@ -12,7 +12,10 @@ var FILE_SIZE = 1234;
var FILE_PATH = 'test/data'; var FILE_PATH = 'test/data';
/** @const {string} */ /** @const {string} */
var GOOGLE_DRIVE = 'Google Drive'; var DEVICE = importer.Destination.DEVICE;
/** @const {string} */
var GOOGLE_DRIVE = importer.Destination.GOOGLE_DRIVE;
/** /**
* Space Cloud: Your source for interstellar cloud storage. * Space Cloud: Your source for interstellar cloud storage.
...@@ -32,6 +35,9 @@ var storage; ...@@ -32,6 +35,9 @@ var storage;
/** @type {!Promise.<!importer.PersistentImportHistory>|undefined} */ /** @type {!Promise.<!importer.PersistentImportHistory>|undefined} */
var historyProvider; var historyProvider;
/** @type {Promise} */
var testPromise;
// Set up the test components. // Set up the test components.
function setUp() { function setUp() {
testFileEntry = new MockFileEntry( testFileEntry = new MockFileEntry(
...@@ -49,53 +55,95 @@ function setUp() { ...@@ -49,53 +55,95 @@ function setUp() {
historyProvider = history.refresh(); historyProvider = history.refresh();
} }
function testHistoryWasCopiedFalseForUnknownEntry(callback) {
// TestRecordWriter is pre-configured with a Space Cloud entry
// but not for this file.
testPromise = historyProvider.then(
function(history) {
history.wasCopied(testFileEntry, SPACE_CAMP).then(assertFalse);
});
reportPromise(testPromise, callback);
}
function testHistoryWasCopiedTrueForKnownEntryLoadedFromStorage(callback) {
// TestRecordWriter is pre-configured with this entry.
testPromise = historyProvider.then(
function(history) {
history.wasCopied(testFileEntry, GOOGLE_DRIVE).then(assertTrue);
});
reportPromise(testPromise, callback);
}
function testHistoryWasImportedTrueForKnownEntrySetAtRuntime(callback) {
testPromise = historyProvider.then(
function(history) {
history.markImported(testFileEntry, SPACE_CAMP).then(
function() {
history.wasImported(testFileEntry, SPACE_CAMP).then(assertTrue);
});
});
reportPromise(testPromise, callback);
}
function testCopyChangeFiresChangedEvent(callback) {
testPromise = historyProvider.then(
function(history) {
var recorder = new TestCallRecorder();
history.addObserver(recorder.callback);
history.markCopied(testFileEntry, SPACE_CAMP, 'url1').then(
function() {
Promise.resolve()
.then(
function() {
recorder.assertCallCount(1);
assertEquals(
importer.ImportHistory.State.COPIED,
recorder.getLastArguments()[0]['state']);
});
});
});
reportPromise(testPromise, callback);
}
function testHistoryWasImportedFalseForUnknownEntry(callback) { function testHistoryWasImportedFalseForUnknownEntry(callback) {
// TestRecordWriter is pre-configured with a Space Cloud entry // TestRecordWriter is pre-configured with a Space Cloud entry
// but not for this file. // but not for this file.
historyProvider.then( testPromise = historyProvider.then(
function(history) { function(history) {
history.wasImported(testFileEntry, SPACE_CAMP).then( history.wasImported(testFileEntry, SPACE_CAMP).then(assertFalse);
function(result) { });
callback(/* error */ result);
}, reportPromise(testPromise, callback);
callback.bind(null, true));
},
callback.bind(null, true))
.catch(handleError.bind(null, callback));
} }
function testHistoryWasImportedTrueForKnownEntryLoadedFromStorage(callback) { function testHistoryWasImportedTrueForKnownEntryLoadedFromStorage(callback) {
// TestRecordWriter is pre-configured with this entry. // TestRecordWriter is pre-configured with this entry.
historyProvider.then( testPromise = historyProvider.then(
function(history) { function(history) {
history.wasImported(testFileEntry, GOOGLE_DRIVE).then( history.wasImported(testFileEntry, GOOGLE_DRIVE).then(assertTrue);
function(result) { });
callback(/* error */ !result);
}, reportPromise(testPromise, callback);
callback.bind(null, true));
},
callback.bind(null, true))
.catch(handleError.bind(null, callback));
} }
function testHistoryWasImportedTrueForKnownEntrySetAtRuntime(callback) { function testHistoryWasImportedTrueForKnownEntrySetAtRuntime(callback) {
historyProvider.then( testPromise = historyProvider.then(
function(history) { function(history) {
history.markImported(testFileEntry, SPACE_CAMP).then( history.markImported(testFileEntry, SPACE_CAMP).then(
function() { function() {
history.wasImported(testFileEntry, SPACE_CAMP).then( history.wasImported(testFileEntry, SPACE_CAMP).then(assertTrue);
function(result) { });
callback(/* error */ !result); });
});
}, reportPromise(testPromise, callback);
callback.bind(null, true));
},
callback.bind(null, true))
.catch(handleError.bind(null, callback));
} }
function testHistoryChangeFiresChangedEvent(callback) { function testImportChangeFiresChangedEvent(callback) {
historyProvider.then( testPromise = historyProvider.then(
function(history) { function(history) {
var recorder = new TestCallRecorder(); var recorder = new TestCallRecorder();
history.addObserver(recorder.callback); history.addObserver(recorder.callback);
...@@ -108,53 +156,50 @@ function testHistoryChangeFiresChangedEvent(callback) { ...@@ -108,53 +156,50 @@ function testHistoryChangeFiresChangedEvent(callback) {
assertEquals( assertEquals(
importer.ImportHistory.State.IMPORTED, importer.ImportHistory.State.IMPORTED,
recorder.getLastArguments()[0]['state']); recorder.getLastArguments()[0]['state']);
callback(false); });
}) });
.catch(handleError.bind(null, callback)); });
},
callback.bind(null, true)); reportPromise(testPromise, callback);
},
callback.bind(null, true))
.catch(handleError.bind(null, callback));
} }
function testHistoryObserverUnsubscribe(callback) { function testHistoryObserverUnsubscribe(callback) {
historyProvider.then( testPromise = historyProvider.then(
function(history) { function(history) {
var recorder = new TestCallRecorder(); var recorder = new TestCallRecorder();
history.addObserver(recorder.callback); history.addObserver(recorder.callback);
history.removeObserver(recorder.callback); history.removeObserver(recorder.callback);
history.markImported(testFileEntry, SPACE_CAMP).then(
var promises = [];
promises.push(history.markCopied(testFileEntry, SPACE_CAMP, 'url2'));
promises.push(history.markImported(testFileEntry, SPACE_CAMP));
Promise.all(promises).then(
function() { function() {
Promise.resolve() Promise.resolve()
.then( .then(
function() { function() {
recorder.assertCallCount(0); recorder.assertCallCount(0);
callback(false); });
}) });
.catch(handleError.bind(null, callback)); });
},
callback.bind(null, true)); reportPromise(testPromise, callback);
},
callback.bind(null, true))
.catch(handleError.bind(null, callback));
} }
function testRecordStorageRemembersPreviouslyWrittenRecords(callback) { function testRecordStorageRemembersPreviouslyWrittenRecords(callback) {
createRealStorage('recordStorageTest.data') testPromise = createRealStorage('recordStorageTest.data')
.then( .then(
function(storage) { function(storage) {
storage.write(['abc', '123']).then( storage.write(['abc', '123']).then(
function() { function() {
storage.readAll().then( storage.readAll().then(
function(records) { function(records) {
callback(/* error */ records.length != 1); assertTrue(records.length != 1);
}, });
callback);
}); });
}, });
callback)
.catch(handleError.bind(null, callback)); reportPromise(testPromise, callback);
} }
function testHistoryLoaderIntegration(callback) { function testHistoryLoaderIntegration(callback) {
...@@ -171,7 +216,7 @@ function testHistoryLoaderIntegration(callback) { ...@@ -171,7 +216,7 @@ function testHistoryLoaderIntegration(callback) {
/** @type {!TestSyncFileEntryProvider|undefined} */ /** @type {!TestSyncFileEntryProvider|undefined} */
var syncFileProvider; var syncFileProvider;
createFileEntry('historyLoaderTest.data') testPromise = createFileEntry('historyLoaderTest.data')
.then( .then(
/** /**
* @param {!FileEntry} fileEntry * @param {!FileEntry} fileEntry
...@@ -208,6 +253,8 @@ function testHistoryLoaderIntegration(callback) { ...@@ -208,6 +253,8 @@ function testHistoryLoaderIntegration(callback) {
function() { function() {
syncFileProvider.fireSyncEvent(); syncFileProvider.fireSyncEvent();
}) })
// TODO(smckay): Add markCopied support once FileWriter issues
// are resolved.
.then( .then(
/** /**
* @return {!Promise.<boolean>} Resolves with true if the * @return {!Promise.<boolean>} Resolves with true if the
...@@ -218,9 +265,10 @@ function testHistoryLoaderIntegration(callback) { ...@@ -218,9 +265,10 @@ function testHistoryLoaderIntegration(callback) {
}) })
.then( .then(
function(wasImported) { function(wasImported) {
callback(/* error */ !wasImported); assertTrue(wasImported);
}) });
.catch(handleError.bind(null, callback));
reportPromise(testPromise, callback);
} }
/** /**
...@@ -285,8 +333,9 @@ var TestRecordStorage = function() { ...@@ -285,8 +333,9 @@ var TestRecordStorage = function() {
// Pre-populate the store with some "previously written" data <wink>. // Pre-populate the store with some "previously written" data <wink>.
/** @private {!Array.<!Array.<string>>} */ /** @private {!Array.<!Array.<string>>} */
this.records_ = [ this.records_ = [
[FILE_LAST_MODIFIED + '_' + FILE_SIZE, GOOGLE_DRIVE], [1, FILE_LAST_MODIFIED + '_' + FILE_SIZE, GOOGLE_DRIVE],
['99999_99999', SPACE_CAMP] [0, FILE_LAST_MODIFIED + '_' + FILE_SIZE, 'google-drive', 'url4'],
[1, '99999_99999', SPACE_CAMP]
]; ];
/** /**
......
...@@ -204,10 +204,14 @@ importer.MediaImportHandler.ImportTask.prototype.onProgress_ = ...@@ -204,10 +204,14 @@ importer.MediaImportHandler.ImportTask.prototype.onProgress_ =
/** @param {!FileEntry} entry */ /** @param {!FileEntry} entry */
importer.MediaImportHandler.ImportTask.prototype.markAsCopied_ = importer.MediaImportHandler.ImportTask.prototype.markAsCopied_ =
function(entry) { function(entry) {
var destinationUrl = this.destination_.toURL() + '/' + entry.name;
this.historyLoader_.getHistory().then( this.historyLoader_.getHistory().then(
/** @param {!importer.ImportHistory} history */ /** @param {!importer.ImportHistory} history */
function(history) { function(history) {
history.markImported(entry, importer.Destination.GOOGLE_DRIVE); history.markCopied(
entry,
importer.Destination.GOOGLE_DRIVE,
destinationUrl);
}); });
}; };
......
...@@ -163,7 +163,7 @@ function testUpdatesHistoryAfterImport(callback) { ...@@ -163,7 +163,7 @@ function testUpdatesHistoryAfterImport(callback) {
importedMedia.forEach( importedMedia.forEach(
/** @param {!CopyCapture} */ /** @param {!CopyCapture} */
function(capture) { function(capture) {
importHistory.assertImported( importHistory.assertCopied(
capture.source, importer.Destination.GOOGLE_DRIVE); capture.source, importer.Destination.GOOGLE_DRIVE);
}); });
}), }),
......
...@@ -15,8 +15,12 @@ var importer = importer || {}; ...@@ -15,8 +15,12 @@ var importer = importer || {};
* @implements {importer.ImportHistory} * @implements {importer.ImportHistory}
*/ */
importer.TestImportHistory = function() { importer.TestImportHistory = function() {
/** @type {!Object.<string, !Object.<!importer.Destination, string>>} */
this.copiedPaths = {};
/** @type {!Object.<string, Array.<string>>} */ /** @type {!Object.<string, Array.<string>>} */
this.importedPaths = {}; this.importedPaths = {};
}; };
/** @override */ /** @override */
...@@ -25,6 +29,47 @@ importer.TestImportHistory.prototype.getHistory = ...@@ -25,6 +29,47 @@ importer.TestImportHistory.prototype.getHistory =
return Promise.resolve(this); return Promise.resolve(this);
}; };
/**
* @param {!FileEntry} entry
* @param {!importer.Destination} destination
*/
importer.TestImportHistory.prototype.assertCopied =
function(entry, destination) {
assertTrue(this.wasCopied_(entry, destination));
};
/**
* Fully synchronous version of wasCopied.
* @param {!FileEntry} entry
* @param {!importer.Destination} destination
* @return {boolean}
*/
importer.TestImportHistory.prototype.wasCopied_ =
function(entry, destination) {
var path = entry.fullPath;
return path in this.copiedPaths &&
this.copiedPaths[path].indexOf(destination) > -1;
};
/** @override */
importer.TestImportHistory.prototype.wasCopied =
function(entry, destination) {
var path = entry.fullPath;
return Promise.resolve(this.wasCopied_(entry, destination));
};
/** @override */
importer.TestImportHistory.prototype.markCopied =
function(entry, destination) {
var path = entry.fullPath;
if (path in this.copiedPaths) {
this.copiedPaths[path].push(destination);
} else {
this.copiedPaths[path] = [destination];
}
return Promise.resolve();
};
/** /**
* @param {!FileEntry} entry * @param {!FileEntry} entry
* @param {!importer.Destination} destination * @param {!importer.Destination} destination
......
...@@ -29,6 +29,8 @@ importer.ELIGIBLE_VOLUME_TYPES_ = [ ...@@ -29,6 +29,8 @@ importer.ELIGIBLE_VOLUME_TYPES_ = [
* @enum {string} * @enum {string}
*/ */
importer.Destination = { importer.Destination = {
// locally copied, but not imported to cloud as of yet.
DEVICE: 'device',
GOOGLE_DRIVE: 'google-drive' GOOGLE_DRIVE: 'google-drive'
}; };
......
...@@ -750,6 +750,20 @@ body[type='full-page'] .detail-name .detail-icon { ...@@ -750,6 +750,20 @@ body[type='full-page'] .detail-name .detail-icon {
height: 30px; height: 30px;
} }
.badge {
height: 16px;
position: absolute;
right: 7px;
top: 7px;
width: 16px;
}
.copied .badge {
background-image: -webkit-image-set(
url(../images/files/ui/copied_badge.png) 1x,
url(../images/files/ui/2x/copied_badge.png) 2x);
}
.imported .badge { .imported .badge {
background-image: -webkit-image-set( background-image: -webkit-image-set(
url(../images/files/ui/drive_badge.png) 1x, url(../images/files/ui/drive_badge.png) 1x,
...@@ -761,7 +775,8 @@ body[type='full-page'] .detail-name .detail-icon { ...@@ -761,7 +775,8 @@ body[type='full-page'] .detail-name .detail-icon {
width: 16px; width: 16px;
} }
.imported .filename-label { .imported .filename-label,
.copied .filename-label {
margin-right: 14px; margin-right: 14px;
} }
......
...@@ -294,11 +294,23 @@ FileGrid.applyHistoryBadges_ = function(entry, box, history) { ...@@ -294,11 +294,23 @@ FileGrid.applyHistoryBadges_ = function(entry, box, history) {
// a possibly-fragile sibling selector we just // a possibly-fragile sibling selector we just
// plop the imported class on the parent of both. // plop the imported class on the parent of both.
box.parentElement.classList.add('imported'); box.parentElement.classList.add('imported');
} else {
history.wasCopied(entry, importer.Destination.GOOGLE_DRIVE)
.then(
function(copied) {
if (copied) {
// TODO(smckay): update badges when history changes
// "box" is currently the sibling of the elemement
// we want to style. So rather than employing
// a possibly-fragile sibling selector we just
// plop the imported class on the parent of both.
box.parentElement.classList.add('copied');
}
});
} }
}); });
}; };
/** /**
* Item for the Grid View. * Item for the Grid View.
* @constructor * @constructor
......
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