Refactor backup.js to use async/await

This commit is contained in:
Scott Nonnenberg 2018-03-08 11:23:19 -08:00 committed by Scott Nonnenberg
parent c01b343bd4
commit 81e94c5aa3
No known key found for this signature in database
GPG key ID: 5F82280C35134661

View file

@ -7,7 +7,7 @@
/* eslint-env node */ /* eslint-env node */
/* eslint-disable no-param-reassign, more/no-then, guard-for-in */ /* eslint-disable no-param-reassign, guard-for-in */
'use strict'; 'use strict';
@ -65,6 +65,7 @@ const {
let wait = Promise.resolve(); let wait = Promise.resolve();
return { return {
write(string) { write(string) {
// eslint-disable-next-line more/no-then
wait = wait.then(() => new Promise(((resolve) => { wait = wait.then(() => new Promise(((resolve) => {
if (writer.write(string)) { if (writer.write(string)) {
resolve(); resolve();
@ -80,12 +81,13 @@ const {
}))); })));
return wait; return wait;
}, },
close() { async close() {
return wait.then(() => new Promise(((resolve, reject) => { await wait;
return new Promise(((resolve, reject) => {
writer.once('finish', resolve); writer.once('finish', resolve);
writer.once('error', reject); writer.once('error', reject);
writer.end(); writer.end();
}))); }));
}, },
}; };
} }
@ -149,7 +151,7 @@ const {
reject reject
); );
}; };
request.onsuccess = (event) => { request.onsuccess = async (event) => {
if (count === 0) { if (count === 0) {
console.log('cursor opened'); console.log('cursor opened');
stream.write(`"${storeName}": [`); stream.write(`"${storeName}": [`);
@ -176,10 +178,9 @@ const {
console.log('Exported all stores'); console.log('Exported all stores');
stream.write('}'); stream.write('}');
stream.close().then(() => { await stream.close();
console.log('Finished writing all stores to disk'); console.log('Finished writing all stores to disk');
resolve(); resolve();
});
} }
} }
}; };
@ -417,26 +418,20 @@ const {
return name; return name;
} }
function readAttachment(parent, message, attachment) { async function readAttachment(parent, message, attachment) {
return new Promise(((resolve, reject) => {
const name = getAttachmentFileName(attachment); const name = getAttachmentFileName(attachment);
const sanitized = sanitizeFileName(name); const sanitized = sanitizeFileName(name);
const attachmentDir = path.join(parent, message.received_at.toString()); const attachmentDir = path.join(parent, message.received_at.toString());
return readFileAsArrayBuffer(attachmentDir, sanitized).then((contents) => { attachment.data = await readFileAsArrayBuffer(attachmentDir, sanitized);
attachment.data = contents;
return resolve();
}, reject);
}));
} }
function writeAttachment(dir, attachment) { async function writeAttachment(dir, attachment) {
const filename = getAttachmentFileName(attachment); const filename = getAttachmentFileName(attachment);
return createFileAndWriter(dir, filename).then((writer) => { const writer = await createFileAndWriter(dir, filename);
const stream = createOutputStream(writer); const stream = createOutputStream(writer);
stream.write(Buffer.from(attachment.data)); stream.write(Buffer.from(attachment.data));
return stream.close(); return stream.close();
});
} }
async function writeAttachments(parentDir, name, messageId, attachments) { async function writeAttachments(parentDir, name, messageId, attachments) {
@ -459,11 +454,10 @@ const {
return filename.toString().replace(/[^a-z0-9.,+()'#\- ]/gi, '_'); return filename.toString().replace(/[^a-z0-9.,+()'#\- ]/gi, '_');
} }
function exportConversation(db, name, conversation, dir) { async function exportConversation(db, name, conversation, dir) {
console.log('exporting conversation', name); console.log('exporting conversation', name);
const writerPromise = createFileAndWriter(dir, 'messages.json'); const writer = await createFileAndWriter(dir, 'messages.json');
return new Promise(((resolve, reject) => {
return writerPromise.then(writer => new Promise(((resolve, reject) => {
const transaction = db.transaction('messages', 'readwrite'); const transaction = db.transaction('messages', 'readwrite');
transaction.onerror = () => { transaction.onerror = () => {
Whisper.Database.handleDOMException( Whisper.Database.handleDOMException(
@ -497,7 +491,7 @@ const {
reject reject
); );
}; };
request.onsuccess = (event) => { request.onsuccess = async (event) => {
const cursor = event.target.result; const cursor = event.target.result;
if (cursor) { if (cursor) {
const message = cursor.value; const message = cursor.value;
@ -524,31 +518,35 @@ const {
if (attachments && attachments.length) { if (attachments && attachments.length) {
const process = () => writeAttachments(dir, name, messageId, attachments); const process = () => writeAttachments(dir, name, messageId, attachments);
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(process); promiseChain = promiseChain.then(process);
} }
count += 1; count += 1;
cursor.continue(); cursor.continue();
} else { } else {
stream.write(']}'); try {
await Promise.all([
const promise = stream.close(); stream.write(']}'),
promiseChain,
promiseChain.then(promise).then(() => { stream.close(),
console.log('done exporting conversation', name); ]);
return resolve(); } catch (error) {
}, (error) => {
console.log( console.log(
'exportConversation: error exporting conversation', 'exportConversation: error exporting conversation',
name, name,
':', ':',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return reject(error); reject(error);
}); return;
}
console.log('done exporting conversation', name);
resolve();
} }
}; };
}))); }));
} }
// Goals for directory names: // Goals for directory names:
@ -602,7 +600,7 @@ const {
reject reject
); );
}; };
request.onsuccess = (event) => { request.onsuccess = async (event) => {
const cursor = event.target.result; const cursor = event.target.result;
if (cursor && cursor.value) { if (cursor && cursor.value) {
const conversation = cursor.value; const conversation = cursor.value;
@ -615,11 +613,18 @@ const {
}; };
console.log('scheduling export for conversation', name); console.log('scheduling export for conversation', name);
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(process); promiseChain = promiseChain.then(process);
cursor.continue(); cursor.continue();
} else { } else {
console.log('Done scheduling conversation exports'); console.log('Done scheduling conversation exports');
promiseChain.then(resolve, reject); try {
await promiseChain;
} catch (error) {
reject(error);
return;
}
resolve();
} }
}; };
})); }));
@ -734,7 +739,7 @@ const {
// message, save it, and only then do we move on to the next message. Thus, every // 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 // message with attachments needs to be removed from our overall message save with the
// filter() call. // filter() call.
function importConversation(db, dir, options) { async function importConversation(db, dir, options) {
options = options || {}; options = options || {};
_.defaults(options, { messageLookup: {} }); _.defaults(options, { messageLookup: {} });
@ -742,8 +747,14 @@ const {
let conversationId = 'unknown'; let conversationId = 'unknown';
let total = 0; let total = 0;
let skipped = 0; let skipped = 0;
let contents;
try {
contents = await readFileAsText(dir, 'messages.json');
} catch (error) {
console.log(`Warning: could not access messages.json in directory: ${dir}`);
}
return readFileAsText(dir, 'messages.json').then((contents) => {
let promiseChain = Promise.resolve(); let promiseChain = Promise.resolve();
const json = JSON.parse(contents); const json = JSON.parse(contents);
@ -766,6 +777,7 @@ const {
return saveMessage(db, message); return saveMessage(db, message);
}; };
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(process); promiseChain = promiseChain.then(process);
return false; return false;
@ -774,14 +786,11 @@ const {
return true; return true;
}); });
let promise = Promise.resolve();
if (messages.length > 0) { if (messages.length > 0) {
promise = saveAllMessages(db, messages); await saveAllMessages(db, messages);
} }
return promise await promiseChain;
.then(() => promiseChain)
.then(() => {
console.log( console.log(
'Finished importing conversation', 'Finished importing conversation',
conversationId, conversationId,
@ -790,14 +799,11 @@ const {
'Skipped:', 'Skipped:',
skipped skipped
); );
});
}, () => {
console.log(`Warning: could not access messages.json in directory: ${dir}`);
});
} }
function importConversations(db, dir, options) { async function importConversations(db, dir, options) {
return getDirContents(dir).then((contents) => { const contents = await getDirContents(dir);
let promiseChain = Promise.resolve(); let promiseChain = Promise.resolve();
_.forEach(contents, (conversationDir) => { _.forEach(contents, (conversationDir) => {
@ -807,11 +813,11 @@ const {
const process = () => importConversation(db, conversationDir, options); const process = () => importConversation(db, conversationDir, options);
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(process); promiseChain = promiseChain.then(process);
}); });
return promiseChain; return promiseChain;
});
} }
function getMessageKey(message) { function getMessageKey(message) {
@ -894,28 +900,23 @@ const {
}; };
return getDirectory(options); return getDirectory(options);
}, },
exportToDirectory(directory, options) { async exportToDirectory(directory, options) {
let dir;
let db;
return Whisper.Database.open().then((openedDb) => {
db = openedDb;
const name = `Signal Export ${getTimestamp()}`; const name = `Signal Export ${getTimestamp()}`;
return createDirectory(directory, name); try {
}).then((created) => { const db = await Whisper.Database.open();
dir = created; const dir = await createDirectory(directory, name);
return exportNonMessages(db, dir, options); await exportNonMessages(db, dir, options);
}).then(() => exportConversations(db, dir)) await exportConversations(db, dir);
.then(() => dir)
.then((targetPath) => {
console.log('done backing up!'); console.log('done backing up!');
return targetPath; return dir;
}, (error) => { } catch (error) {
console.log( console.log(
'the backup went wrong:', 'the backup went wrong:',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return Promise.reject(error); throw error;
}); }
}, },
getDirectoryForImport() { getDirectoryForImport() {
const options = { const options = {
@ -924,41 +925,34 @@ const {
}; };
return getDirectory(options); return getDirectory(options);
}, },
importFromDirectory(directory, options) { async importFromDirectory(directory, options) {
options = options || {}; options = options || {};
let db; try {
let nonMessageResult; const db = await Whisper.Database.open();
return Whisper.Database.open().then((createdDb) => { const lookups = await Promise.all([
db = createdDb;
return Promise.all([
loadMessagesLookup(db), loadMessagesLookup(db),
loadConversationLookup(db), loadConversationLookup(db),
loadGroupsLookup(db), loadGroupsLookup(db),
]); ]);
}).then((lookups) => {
const [messageLookup, conversationLookup, groupLookup] = lookups; const [messageLookup, conversationLookup, groupLookup] = lookups;
options = Object.assign({}, options, { options = Object.assign({}, options, {
messageLookup, messageLookup,
conversationLookup, conversationLookup,
groupLookup, groupLookup,
}); });
}).then(() => importNonMessages(db, directory, options))
.then((result) => { const result = await importNonMessages(db, directory, options);
nonMessageResult = result; await importConversations(db, directory, options);
return importConversations(db, directory, options);
})
.then(() => {
console.log('done restoring from backup!'); console.log('done restoring from backup!');
return nonMessageResult; return result;
}, (error) => { } catch (error) {
console.log( console.log(
'the import went wrong:', 'the import went wrong:',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return Promise.reject(error); throw error;
}); }
}, },
// for testing // for testing
sanitizeFileName, sanitizeFileName,