Refactor backup.js to use async/await
This commit is contained in:
parent
c01b343bd4
commit
81e94c5aa3
1 changed files with 143 additions and 149 deletions
160
js/backup.js
160
js/backup.js
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue