Move conversations to SQLCipher

This commit is contained in:
Scott Nonnenberg 2018-09-20 18:47:19 -07:00
parent 8cd3db0262
commit cd60bdd08a
31 changed files with 1354 additions and 774 deletions

View file

@ -15,6 +15,18 @@ module.exports = {
close,
removeDB,
getConversationCount,
saveConversation,
saveConversations,
getConversationById,
updateConversation,
removeConversation,
getAllConversations,
getAllConversationIds,
getAllPrivateConversations,
getAllGroupsInvolvingId,
searchConversations,
getMessageCount,
saveMessage,
saveMessages,
@ -22,6 +34,7 @@ module.exports = {
getUnreadByConversation,
getMessageBySender,
getMessageById,
getAllMessages,
getAllMessageIds,
getMessagesBySentAt,
getExpiredMessages,
@ -270,10 +283,47 @@ async function updateToSchemaVersion3(currentVersion, instance) {
console.log('updateToSchemaVersion3: success!');
}
async function updateToSchemaVersion4(currentVersion, instance) {
if (currentVersion >= 4) {
return;
}
console.log('updateToSchemaVersion4: starting...');
await instance.run('BEGIN TRANSACTION;');
await instance.run(
`CREATE TABLE conversations(
id STRING PRIMARY KEY ASC,
json TEXT,
active_at INTEGER,
type STRING,
members TEXT,
name TEXT,
profileName TEXT
);`
);
await instance.run(`CREATE INDEX conversations_active ON conversations (
active_at
) WHERE active_at IS NOT NULL;`);
await instance.run(`CREATE INDEX conversations_type ON conversations (
type
) WHERE type IS NOT NULL;`);
await instance.run('PRAGMA schema_version = 4;');
await instance.run('COMMIT TRANSACTION;');
console.log('updateToSchemaVersion4: success!');
}
const SCHEMA_VERSIONS = [
updateToSchemaVersion1,
updateToSchemaVersion2,
updateToSchemaVersion3,
updateToSchemaVersion4,
];
async function updateSchema(instance) {
@ -348,6 +398,190 @@ async function removeDB() {
rimraf.sync(filePath);
}
async function getConversationCount() {
const row = await db.get('SELECT count(*) from conversations;');
if (!row) {
throw new Error('getMessageCount: Unable to get count of conversations');
}
return row['count(*)'];
}
async function saveConversation(data) {
// eslint-disable-next-line camelcase
const { id, active_at, type, members, name, profileName } = data;
await db.run(
`INSERT INTO conversations (
id,
json,
active_at,
type,
members,
name,
profileName
) values (
$id,
$json,
$active_at,
$type,
$members,
$name,
$profileName
);`,
{
$id: id,
$json: objectToJSON(data),
$active_at: active_at,
$type: type,
$members: members ? members.join(' ') : null,
$name: name,
$profileName: profileName,
}
);
}
async function saveConversations(arrayOfConversations) {
let promise;
db.serialize(() => {
promise = Promise.all([
db.run('BEGIN TRANSACTION;'),
...map(arrayOfConversations, conversation =>
saveConversation(conversation)
),
db.run('COMMIT TRANSACTION;'),
]);
});
await promise;
}
async function updateConversation(data) {
// eslint-disable-next-line camelcase
const { id, active_at, type, members, name, profileName } = data;
await db.run(
`UPDATE conversations SET
json = $json,
active_at = $active_at,
type = $type,
members = $members,
name = $name,
profileName = $profileName
WHERE id = $id;`,
{
$id: id,
$json: objectToJSON(data),
$active_at: active_at,
$type: type,
$members: members ? members.join(' ') : null,
$name: name,
$profileName: profileName,
}
);
}
async function removeConversation(id) {
if (!Array.isArray(id)) {
await db.run('DELETE FROM conversations WHERE id = $id;', { $id: id });
return;
}
if (!id.length) {
throw new Error('removeConversation: No ids to delete!');
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run(
`DELETE FROM conversations WHERE id IN ( ${id
.map(() => '?')
.join(', ')} );`,
id
);
}
async function getConversationById(id) {
const row = await db.get('SELECT * FROM conversations WHERE id = $id;', {
$id: id,
});
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function getAllConversations() {
const rows = await db.all('SELECT json FROM conversations ORDER BY id ASC;');
return map(rows, row => jsonToObject(row.json));
}
async function getAllConversationIds() {
const rows = await db.all('SELECT id FROM conversations ORDER BY id ASC;');
return map(rows, row => row.id);
}
async function getAllPrivateConversations() {
const rows = await db.all(
`SELECT json FROM conversations WHERE
type = 'private'
ORDER BY id ASC;`
);
if (!rows) {
return null;
}
return map(rows, row => jsonToObject(row.json));
}
async function getAllGroupsInvolvingId(id) {
const rows = await db.all(
`SELECT json FROM conversations WHERE
type = 'group' AND
members LIKE $id
ORDER BY id ASC;`,
{
$id: `%${id}%`,
}
);
if (!rows) {
return null;
}
return map(rows, row => jsonToObject(row.json));
}
async function searchConversations(query) {
const rows = await db.all(
`SELECT json FROM conversations WHERE
id LIKE $id OR
name LIKE $name OR
profileName LIKE $profileName
ORDER BY id ASC;`,
{
$id: `%${query}%`,
$name: `%${query}%`,
$profileName: `%${query}%`,
}
);
if (!rows) {
return null;
}
return map(rows, row => jsonToObject(row.json));
}
async function getMessageCount() {
const row = await db.get('SELECT count(*) from messages;');
@ -522,6 +756,11 @@ async function getMessageById(id) {
return jsonToObject(row.json);
}
async function getAllMessages() {
const rows = await db.all('SELECT json FROM messages ORDER BY id ASC;');
return map(rows, row => jsonToObject(row.json));
}
async function getAllMessageIds() {
const rows = await db.all('SELECT id FROM messages ORDER BY id ASC;');
return map(rows, row => row.id);
@ -764,6 +1003,7 @@ async function removeAll() {
db.run('BEGIN TRANSACTION;'),
db.run('DELETE FROM messages;'),
db.run('DELETE FROM unprocessed;'),
db.run('DELETE from conversations;'),
db.run('COMMIT TRANSACTION;'),
]);
});
@ -874,6 +1114,21 @@ function getExternalFilesForMessage(message) {
return files;
}
function getExternalFilesForConversation(conversation) {
const { avatar, profileAvatar } = conversation;
const files = [];
if (avatar && avatar.path) {
files.push(avatar.path);
}
if (profileAvatar && profileAvatar.path) {
files.push(profileAvatar.path);
}
return files;
}
async function removeKnownAttachments(allAttachments) {
const lookup = fromPairs(map(allAttachments, file => [file, true]));
const chunkSize = 50;
@ -918,5 +1173,47 @@ async function removeKnownAttachments(allAttachments) {
console.log(`removeKnownAttachments: Done processing ${count} messages`);
complete = false;
count = 0;
// Though conversations.id is a string, this ensures that, when coerced, this
// value is still a string but it's smaller than every other string.
id = 0;
const conversationTotal = await getConversationCount();
console.log(
`removeKnownAttachments: About to iterate through ${conversationTotal} conversations`
);
while (!complete) {
// eslint-disable-next-line no-await-in-loop
const rows = await db.all(
`SELECT json FROM conversations
WHERE id > $id
ORDER BY id ASC
LIMIT $chunkSize;`,
{
$id: id,
$chunkSize: chunkSize,
}
);
const conversations = map(rows, row => jsonToObject(row.json));
forEach(conversations, conversation => {
const externalFiles = getExternalFilesForConversation(conversation);
forEach(externalFiles, file => {
delete lookup[file];
});
});
const lastMessage = last(conversations);
if (lastMessage) {
({ id } = lastMessage);
}
complete = conversations.length < chunkSize;
count += conversations.length;
}
console.log(`removeKnownAttachments: Done processing ${count} conversations`);
return Object.keys(lookup);
}