Import: Better onerror logging, save attachments serially (#1768)

* Import: Proper error handling and reporting from IndexedDB APIs

* Import: Load attachments one at a time, not per-conversation
This commit is contained in:
Scott Nonnenberg 2017-11-14 16:40:43 -08:00 committed by GitHub
parent f0ec75eef4
commit bd65932d94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -188,7 +188,14 @@
}; };
var transaction = idb_db.transaction(storeNames, 'readwrite'); var transaction = idb_db.transaction(storeNames, 'readwrite');
transaction.onerror = reject; transaction.onerror = function() {
var error = transaction.error;
console.log(
'importFromJsonString error:',
error && error.stack ? error.stack : error
);
reject(error || new Error('importFromJsonString: transaction.onerror'));
};
transaction.oncomplete = finish.bind(null, 'transaction complete'); transaction.oncomplete = finish.bind(null, 'transaction complete');
_.each(storeNames, function(storeName) { _.each(storeNames, function(storeName) {
@ -216,14 +223,16 @@
} }
} }
}; };
request.onerror = function(error) { request.onerror = function() {
var error = request.error;
console.log( console.log(
'Error adding object to store', 'Error adding object to store',
storeName, storeName,
':', ':',
toAdd toAdd,
error && error.stack ? error.stack : error
); );
reject(error); reject(error || new Error('importFromJsonString: request.onerror'));
}; };
}); });
}); });
@ -383,14 +392,16 @@
return createFileAndWriter(dir, 'messages.json').then(function(writer) { return createFileAndWriter(dir, 'messages.json').then(function(writer) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction('messages', 'readwrite'); var transaction = idb_db.transaction('messages', 'readwrite');
transaction.onerror = function(e) { transaction.onerror = function() {
var error = transaction.error;
console.log( console.log(
'exportConversation transaction error for conversation', 'exportConversation transaction error for conversation',
name, name,
':', ':',
e && e.stack ? e.stack : e error && error.stack ? error.stack : error
); );
return reject(e); return reject(error || new Error('exportConversation: transaction.onerror'));
}; };
transaction.oncomplete = function() { transaction.oncomplete = function() {
// this doesn't really mean anything - we may have attachment processing to do // this doesn't really mean anything - we may have attachment processing to do
@ -407,14 +418,16 @@
var stream = createOutputStream(writer); var stream = createOutputStream(writer);
stream.write('{"messages":['); stream.write('{"messages":[');
request.onerror = function(e) { request.onerror = function() {
var error = request.error;
console.log( console.log(
'exportConversation: error pulling messages for conversation', 'exportConversation: error pulling messages for conversation',
name, name,
':', ':',
e && e.stack ? e.stack : e error && error.stack ? error.stack : error
); );
return reject(e); return reject(error || new Error('exportConversation: request.onerror'));
}; };
request.onsuccess = function(event) { request.onsuccess = function(event) {
var cursor = event.target.result; var cursor = event.target.result;
@ -497,12 +510,13 @@
function exportConversations(idb_db, parentDir) { function exportConversations(idb_db, parentDir) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction('conversations', 'readwrite'); var transaction = idb_db.transaction('conversations', 'readwrite');
transaction.onerror = function(e) { transaction.onerror = function() {
var error = transaction.error;
console.log( console.log(
'exportConversations: transaction error:', 'exportConversations: transaction error:',
e && e.stack ? e.stack : e error && error.stack ? error.stack : error
); );
return reject(e); return reject(error || new Error('exportConversations: transaction.onerror'));
}; };
transaction.oncomplete = function() { transaction.oncomplete = function() {
// not really very useful - fires at unexpected times // not really very useful - fires at unexpected times
@ -511,12 +525,13 @@
var promiseChain = Promise.resolve(); var promiseChain = Promise.resolve();
var store = transaction.objectStore('conversations'); var store = transaction.objectStore('conversations');
var request = store.openCursor(); var request = store.openCursor();
request.onerror = function(e) { request.onerror = function() {
var error = request.error;
console.log( console.log(
'exportConversations: error pulling conversations:', 'exportConversations: error pulling conversations:',
e && e.stack ? e.stack : e error && error.stack ? error.stack : error
); );
return reject(e); return reject(error || new Error('exportConversations: request.onerror'));
}; };
request.onsuccess = function(event) { request.onsuccess = function(event) {
var cursor = event.target.result; var cursor = event.target.result;
@ -601,12 +616,14 @@
}; };
var transaction = idb_db.transaction('messages', 'readwrite'); var transaction = idb_db.transaction('messages', 'readwrite');
transaction.onerror = function(e) { transaction.onerror = function() {
var error = transaction.error;
console.log( console.log(
'saveAllMessages transaction error:', 'saveAllMessages transaction error:',
e && e.stack ? e.stack : e error && error.stack ? error.stack : error
); );
return reject(e); return reject(error || new Error('saveAllMessages: transaction.onerror'));
}; };
transaction.oncomplete = finish.bind(null, 'transaction complete'); transaction.oncomplete = finish.bind(null, 'transaction complete');
@ -620,7 +637,7 @@
count += 1; count += 1;
if (count === messages.length) { if (count === messages.length) {
console.log( console.log(
'Done importing', 'Saved',
messages.length, messages.length,
'messages for conversation', 'messages for conversation',
// Don't know if group or private conversation, so we blindly redact // Don't know if group or private conversation, so we blindly redact
@ -629,35 +646,63 @@
finish('puts scheduled'); finish('puts scheduled');
} }
}; };
request.onerror = function(event) { request.onerror = function() {
console.log('Error adding object to store:', event); var event = request.error;
reject(new Error('saveAllMessage: onerror fired')); console.log(
'Error adding object to store:',
error && error.stack ? error.stack : error
);
reject(error || new Error('saveAllMessages: request.onerror'));
}; };
}); });
}); });
} }
// To reduce the memory impact of attachments, we make individual saves to the
// database for every message with an attachment. We load the attachment for a
// message, save it, and only then do we move on to the next message. Thus, every
// message with attachments needs to be removed from our overall message save with the
// filter() call.
function importConversation(idb_db, dir) { function importConversation(idb_db, dir) {
return readFileAsText(dir, 'messages.json').then(function(contents) { return readFileAsText(dir, 'messages.json').then(function(contents) {
var promiseChain = Promise.resolve(); var promiseChain = Promise.resolve();
var json = JSON.parse(contents); var json = JSON.parse(contents);
var messages = json.messages; var conversationId;
_.forEach(messages, function(message) { if (json.messages && json.messages.length) {
conversationId = json.messages[0].conversationId;
}
var messages = _.filter(json.messages, function(message) {
message = unstringify(message); message = unstringify(message);
if (message.attachments && message.attachments.length) { if (message.attachments && message.attachments.length) {
var process = function() { var process = function() {
return loadAttachments(dir, message); return loadAttachments(dir, message).then(function() {
return saveAllMessages(idb_db, [message]);
});
}; };
promiseChain = promiseChain.then(process); promiseChain = promiseChain.then(process);
return null;
} }
return message;
}); });
return promiseChain.then(function() { return saveAllMessages(idb_db, messages)
return saveAllMessages(idb_db, messages); .then(function() {
}); return promiseChain;
})
.then(function() {
console.log(
'Finished importing conversation',
// Don't know if group or private conversation, so we blindly redact
conversationId ? '[REDACTED]' + conversationId.slice(-3) : 'with no messages'
);
});
}, function() { }, function() {
console.log('Warning: could not access messages.json in directory: ' + dir); console.log('Warning: could not access messages.json in directory: ' + dir);
}); });
@ -689,10 +734,18 @@
var storeNames = idb_db.objectStoreNames; var storeNames = idb_db.objectStoreNames;
var transaction = idb_db.transaction(storeNames, 'readwrite'); var transaction = idb_db.transaction(storeNames, 'readwrite');
transaction.oncomplete = function() { var finished = false;
// unused var finish = function(via) {
console.log('clearing all stores done via', via);
if (finished) {
resolve();
}
finished = true;
}; };
transaction.onerror = function(error) {
transaction.oncomplete = finish.bind(null, 'transaction complete');
transaction.onerror = function() {
var error = transaction.error;
console.log( console.log(
'saveAllMessages transaction error:', 'saveAllMessages transaction error:',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
@ -711,16 +764,18 @@
if (count >= storeNames.length) { if (count >= storeNames.length) {
console.log('Done clearing all indexeddb stores'); console.log('Done clearing all indexeddb stores');
return resolve(); return finish('clears complete');
} }
}; };
request.onerror = function(error) { request.onerror = function() {
var error = request.error;
console.log( console.log(
'clearAllStores transaction error:', 'clearAllStores transaction error:',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return reject(error); return reject(error || new Error('clearAllStores: request.onerror'));
}; };
}); });
}); });