2018-07-27 01:13:56 +00:00
|
|
|
/* global window, setTimeout */
|
|
|
|
|
|
|
|
const electron = require('electron');
|
2018-09-21 01:47:19 +00:00
|
|
|
|
|
|
|
const { forEach, isFunction, isObject, merge } = require('lodash');
|
2018-07-25 22:02:27 +00:00
|
|
|
|
|
|
|
const { deferredToPromise } = require('./deferred_to_promise');
|
|
|
|
const MessageType = require('./types/message');
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
const { ipcRenderer } = electron;
|
|
|
|
|
|
|
|
// We listen to a lot of events on ipcRenderer, often on the same channel. This prevents
|
|
|
|
// any warnings that might be sent to the console in that case.
|
|
|
|
ipcRenderer.setMaxListeners(0);
|
|
|
|
|
|
|
|
// calls to search for when finding functions to convert:
|
2018-07-25 22:02:27 +00:00
|
|
|
// .fetch(
|
|
|
|
// .save(
|
|
|
|
// .destroy(
|
|
|
|
|
2018-08-08 19:15:45 +00:00
|
|
|
const DATABASE_UPDATE_TIMEOUT = 2 * 60 * 1000; // two minutes
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
const SQL_CHANNEL_KEY = 'sql-channel';
|
|
|
|
const ERASE_SQL_KEY = 'erase-sql-key';
|
|
|
|
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
2018-08-08 17:00:33 +00:00
|
|
|
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
2018-07-27 01:13:56 +00:00
|
|
|
|
|
|
|
const _jobs = Object.create(null);
|
|
|
|
const _DEBUG = false;
|
|
|
|
let _jobCounter = 0;
|
|
|
|
|
|
|
|
const channels = {};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
_jobs,
|
|
|
|
_cleanData,
|
|
|
|
|
|
|
|
close,
|
|
|
|
removeDB,
|
|
|
|
|
2018-09-21 01:47:19 +00:00
|
|
|
getConversationCount,
|
|
|
|
saveConversation,
|
|
|
|
saveConversations,
|
|
|
|
getConversationById,
|
|
|
|
updateConversation,
|
|
|
|
removeConversation,
|
|
|
|
_removeConversations,
|
|
|
|
|
|
|
|
getAllConversations,
|
|
|
|
getAllConversationIds,
|
|
|
|
getAllPrivateConversations,
|
|
|
|
getAllGroupsInvolvingId,
|
|
|
|
searchConversations,
|
|
|
|
|
2018-08-06 23:18:58 +00:00
|
|
|
getMessageCount,
|
2018-07-27 01:13:56 +00:00
|
|
|
saveMessage,
|
2018-08-01 19:38:48 +00:00
|
|
|
saveLegacyMessage,
|
2018-07-27 01:13:56 +00:00
|
|
|
saveMessages,
|
|
|
|
removeMessage,
|
2018-08-01 02:51:17 +00:00
|
|
|
_removeMessages,
|
2018-07-27 01:13:56 +00:00
|
|
|
getUnreadByConversation,
|
|
|
|
|
|
|
|
removeAllMessagesInConversation,
|
|
|
|
|
|
|
|
getMessageBySender,
|
|
|
|
getMessageById,
|
2018-09-21 01:47:19 +00:00
|
|
|
getAllMessages,
|
2018-07-27 01:13:56 +00:00
|
|
|
getAllMessageIds,
|
|
|
|
getMessagesBySentAt,
|
|
|
|
getExpiredMessages,
|
2018-08-07 19:33:56 +00:00
|
|
|
getOutgoingWithoutExpiresAt,
|
2018-07-27 01:13:56 +00:00
|
|
|
getNextExpiringMessage,
|
|
|
|
getMessagesByConversation,
|
|
|
|
|
|
|
|
getAllUnprocessed,
|
|
|
|
getUnprocessedById,
|
|
|
|
saveUnprocessed,
|
|
|
|
saveUnprocesseds,
|
|
|
|
removeUnprocessed,
|
|
|
|
removeAllUnprocessed,
|
|
|
|
|
|
|
|
removeAll,
|
|
|
|
removeOtherData,
|
2018-08-08 17:00:33 +00:00
|
|
|
cleanupOrphanedAttachments,
|
2018-07-27 01:13:56 +00:00
|
|
|
|
|
|
|
// Returning plain JSON
|
|
|
|
getMessagesNeedingUpgrade,
|
|
|
|
getLegacyMessagesNeedingUpgrade,
|
|
|
|
getMessagesWithVisualMediaAttachments,
|
|
|
|
getMessagesWithFileAttachments,
|
|
|
|
};
|
|
|
|
|
|
|
|
// When IPC arguments are prepared for the cross-process send, they are JSON.stringified.
|
|
|
|
// We can't send ArrayBuffers or BigNumbers (what we get from proto library for dates).
|
|
|
|
function _cleanData(data) {
|
|
|
|
const keys = Object.keys(data);
|
|
|
|
for (let index = 0, max = keys.length; index < max; index += 1) {
|
|
|
|
const key = keys[index];
|
|
|
|
const value = data[key];
|
|
|
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
// eslint-disable-next-line no-continue
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isFunction(value.toNumber)) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
data[key] = value.toNumber();
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
data[key] = value.map(item => _cleanData(item));
|
|
|
|
} else if (isObject(value)) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
data[key] = _cleanData(value);
|
|
|
|
} else if (
|
|
|
|
typeof value !== 'string' &&
|
|
|
|
typeof value !== 'number' &&
|
|
|
|
typeof value !== 'boolean'
|
|
|
|
) {
|
|
|
|
window.log.info(`_cleanData: key ${key} had type ${typeof value}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _makeJob(fnName) {
|
|
|
|
_jobCounter += 1;
|
|
|
|
const id = _jobCounter;
|
|
|
|
|
2018-08-10 19:19:40 +00:00
|
|
|
if (_DEBUG) {
|
|
|
|
window.log.info(`SQL channel job ${id} (${fnName}) started`);
|
|
|
|
}
|
2018-07-27 01:13:56 +00:00
|
|
|
_jobs[id] = {
|
|
|
|
fnName,
|
2018-08-09 01:32:10 +00:00
|
|
|
start: Date.now(),
|
2018-07-27 01:13:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _updateJob(id, data) {
|
|
|
|
const { resolve, reject } = data;
|
2018-08-09 01:32:10 +00:00
|
|
|
const { fnName, start } = _jobs[id];
|
2018-07-27 01:13:56 +00:00
|
|
|
|
|
|
|
_jobs[id] = {
|
|
|
|
..._jobs[id],
|
|
|
|
...data,
|
|
|
|
resolve: value => {
|
|
|
|
_removeJob(id);
|
2018-08-09 01:32:10 +00:00
|
|
|
const end = Date.now();
|
|
|
|
window.log.info(
|
|
|
|
`SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms`
|
|
|
|
);
|
2018-07-27 01:13:56 +00:00
|
|
|
return resolve(value);
|
|
|
|
},
|
|
|
|
reject: error => {
|
|
|
|
_removeJob(id);
|
2018-08-09 01:32:10 +00:00
|
|
|
const end = Date.now();
|
|
|
|
window.log.info(
|
|
|
|
`SQL channel job ${id} (${fnName}) failed in ${end - start}ms`
|
|
|
|
);
|
2018-07-27 01:13:56 +00:00
|
|
|
return reject(error);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function _removeJob(id) {
|
|
|
|
if (_DEBUG) {
|
|
|
|
_jobs[id].complete = true;
|
|
|
|
} else {
|
|
|
|
delete _jobs[id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _getJob(id) {
|
|
|
|
return _jobs[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
ipcRenderer.on(
|
|
|
|
`${SQL_CHANNEL_KEY}-done`,
|
|
|
|
(event, jobId, errorForDisplay, result) => {
|
|
|
|
const job = _getJob(jobId);
|
|
|
|
if (!job) {
|
|
|
|
throw new Error(
|
2018-08-16 17:45:09 +00:00
|
|
|
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
|
2018-07-27 01:13:56 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { resolve, reject, fnName } = job;
|
|
|
|
|
|
|
|
if (errorForDisplay) {
|
|
|
|
return reject(
|
2018-08-16 17:45:09 +00:00
|
|
|
new Error(
|
|
|
|
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
|
|
|
|
)
|
2018-07-27 01:13:56 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(result);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
function makeChannel(fnName) {
|
|
|
|
channels[fnName] = (...args) => {
|
|
|
|
const jobId = _makeJob(fnName);
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ipcRenderer.send(SQL_CHANNEL_KEY, jobId, fnName, ...args);
|
|
|
|
|
|
|
|
_updateJob(jobId, {
|
|
|
|
resolve,
|
|
|
|
reject,
|
|
|
|
args: _DEBUG ? args : null,
|
|
|
|
});
|
|
|
|
|
|
|
|
setTimeout(
|
2018-08-16 17:45:09 +00:00
|
|
|
() =>
|
|
|
|
reject(new Error(`SQL channel job ${jobId} (${fnName}) timed out`)),
|
2018-08-08 19:15:45 +00:00
|
|
|
DATABASE_UPDATE_TIMEOUT
|
2018-07-27 01:13:56 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
forEach(module.exports, fn => {
|
|
|
|
if (isFunction(fn)) {
|
|
|
|
makeChannel(fn.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
async function close() {
|
|
|
|
await channels.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
async function removeDB() {
|
|
|
|
await channels.removeDB();
|
|
|
|
}
|
|
|
|
|
2018-09-21 01:47:19 +00:00
|
|
|
async function getConversationCount() {
|
|
|
|
return channels.getConversationCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function saveConversation(data) {
|
|
|
|
await channels.saveConversation(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function saveConversations(data) {
|
|
|
|
await channels.saveConversations(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getConversationById(id, { Conversation }) {
|
|
|
|
const data = await channels.getConversationById(id);
|
|
|
|
return new Conversation(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateConversation(id, data, { Conversation }) {
|
|
|
|
const existing = await getConversationById(id, { Conversation });
|
|
|
|
if (!existing) {
|
|
|
|
throw new Error(`Conversation ${id} does not exist!`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const merged = merge({}, existing.attributes, data);
|
|
|
|
await channels.updateConversation(merged);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function removeConversation(id, { Conversation }) {
|
|
|
|
const existing = await getConversationById(id, { Conversation });
|
|
|
|
|
|
|
|
// Note: It's important to have a fully database-hydrated model to delete here because
|
|
|
|
// it needs to delete all associated on-disk files along with the database delete.
|
|
|
|
if (existing) {
|
|
|
|
await channels.removeConversation(id);
|
|
|
|
await existing.cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: this method will not clean up external files, just delete from SQL
|
|
|
|
async function _removeConversations(ids) {
|
|
|
|
await channels.removeConversation(ids);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getAllConversations({ ConversationCollection }) {
|
|
|
|
const conversations = await channels.getAllConversations();
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getAllConversationIds() {
|
|
|
|
const ids = await channels.getAllConversationIds();
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getAllPrivateConversations({ ConversationCollection }) {
|
|
|
|
const conversations = await channels.getAllPrivateConversations();
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getAllGroupsInvolvingId(id, { ConversationCollection }) {
|
|
|
|
const conversations = await channels.getAllGroupsInvolvingId(id);
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function searchConversations(query, { ConversationCollection }) {
|
|
|
|
const conversations = await channels.searchConversations(query);
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
2018-08-06 23:18:58 +00:00
|
|
|
async function getMessageCount() {
|
|
|
|
return channels.getMessageCount();
|
|
|
|
}
|
|
|
|
|
2018-08-03 04:12:27 +00:00
|
|
|
async function saveMessage(data, { forceSave, Message } = {}) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const id = await channels.saveMessage(_cleanData(data), { forceSave });
|
2018-08-03 04:12:27 +00:00
|
|
|
Message.refreshExpirationTimer();
|
2018-07-27 01:13:56 +00:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2018-08-01 19:38:48 +00:00
|
|
|
async function saveLegacyMessage(data, { Message }) {
|
|
|
|
const message = new Message(data);
|
|
|
|
await deferredToPromise(message.save());
|
|
|
|
return message.id;
|
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function saveMessages(arrayOfMessages, { forceSave } = {}) {
|
|
|
|
await channels.saveMessages(_cleanData(arrayOfMessages), { forceSave });
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function removeMessage(id, { Message }) {
|
|
|
|
const message = await getMessageById(id, { Message });
|
2018-07-27 01:13:56 +00:00
|
|
|
|
2018-07-25 22:02:27 +00:00
|
|
|
// Note: It's important to have a fully database-hydrated model to delete here because
|
|
|
|
// it needs to delete all associated on-disk files along with the database delete.
|
|
|
|
if (message) {
|
2018-07-27 01:13:56 +00:00
|
|
|
await channels.removeMessage(id);
|
2018-08-17 00:28:12 +00:00
|
|
|
await message.cleanup();
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-01 02:51:17 +00:00
|
|
|
// Note: this method will not clean up external files, just delete from SQL
|
|
|
|
async function _removeMessages(ids) {
|
|
|
|
await channels.removeMessage(ids);
|
|
|
|
}
|
|
|
|
|
2018-07-25 22:02:27 +00:00
|
|
|
async function getMessageById(id, { Message }) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const message = await channels.getMessageById(id);
|
2018-08-09 23:09:41 +00:00
|
|
|
if (!message) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
return new Message(message);
|
2018-09-21 01:47:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// For testing only
|
|
|
|
async function getAllMessages({ MessageCollection }) {
|
|
|
|
const messages = await channels.getAllMessages();
|
|
|
|
return new MessageCollection(messages);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getAllMessageIds() {
|
|
|
|
const ids = await channels.getAllMessageIds();
|
|
|
|
return ids;
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getMessageBySender(
|
|
|
|
// eslint-disable-next-line camelcase
|
|
|
|
{ source, sourceDevice, sent_at },
|
|
|
|
{ Message }
|
|
|
|
) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getMessageBySender({
|
|
|
|
source,
|
|
|
|
sourceDevice,
|
|
|
|
sent_at,
|
|
|
|
});
|
|
|
|
if (!messages || !messages.length) {
|
2018-07-25 22:02:27 +00:00
|
|
|
return null;
|
|
|
|
}
|
2018-07-27 01:13:56 +00:00
|
|
|
|
|
|
|
return new Message(messages[0]);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getUnreadByConversation(conversationId, { MessageCollection }) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getUnreadByConversation(conversationId);
|
|
|
|
return new MessageCollection(messages);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getMessagesByConversation(
|
|
|
|
conversationId,
|
|
|
|
{ limit = 100, receivedAt = Number.MAX_VALUE, MessageCollection }
|
|
|
|
) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getMessagesByConversation(conversationId, {
|
2018-07-25 22:02:27 +00:00
|
|
|
limit,
|
2018-07-27 01:13:56 +00:00
|
|
|
receivedAt,
|
|
|
|
});
|
2018-07-25 22:02:27 +00:00
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
return new MessageCollection(messages);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function removeAllMessagesInConversation(
|
|
|
|
conversationId,
|
|
|
|
{ MessageCollection }
|
|
|
|
) {
|
2018-07-27 01:13:56 +00:00
|
|
|
let messages;
|
2018-07-25 22:02:27 +00:00
|
|
|
do {
|
|
|
|
// Yes, we really want the await in the loop. We're deleting 100 at a
|
|
|
|
// time so we don't use too much memory.
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2018-07-27 01:13:56 +00:00
|
|
|
messages = await getMessagesByConversation(conversationId, {
|
|
|
|
limit: 100,
|
|
|
|
MessageCollection,
|
|
|
|
});
|
2018-07-25 22:02:27 +00:00
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
if (!messages.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ids = messages.map(message => message.id);
|
2018-07-25 22:02:27 +00:00
|
|
|
|
|
|
|
// Note: It's very important that these models are fully hydrated because
|
|
|
|
// we need to delete all associated on-disk files along with the database delete.
|
2018-07-27 01:13:56 +00:00
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await Promise.all(messages.map(message => message.cleanup()));
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await channels.removeMessage(ids);
|
|
|
|
} while (messages.length > 0);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getMessagesBySentAt(sentAt, { MessageCollection }) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getMessagesBySentAt(sentAt);
|
|
|
|
return new MessageCollection(messages);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getExpiredMessages({ MessageCollection }) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getExpiredMessages();
|
|
|
|
return new MessageCollection(messages);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-08-07 19:33:56 +00:00
|
|
|
async function getOutgoingWithoutExpiresAt({ MessageCollection }) {
|
|
|
|
const messages = await channels.getOutgoingWithoutExpiresAt();
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
2018-07-25 22:02:27 +00:00
|
|
|
async function getNextExpiringMessage({ MessageCollection }) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getNextExpiringMessage();
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
2018-07-25 22:02:27 +00:00
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getAllUnprocessed() {
|
|
|
|
return channels.getAllUnprocessed();
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getUnprocessedById(id, { Unprocessed }) {
|
|
|
|
const unprocessed = await channels.getUnprocessedById(id);
|
2018-08-09 23:09:41 +00:00
|
|
|
if (!unprocessed) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
return new Unprocessed(unprocessed);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function saveUnprocessed(data, { forceSave } = {}) {
|
|
|
|
const id = await channels.saveUnprocessed(_cleanData(data), { forceSave });
|
|
|
|
return id;
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function saveUnprocesseds(arrayOfUnprocessed, { forceSave } = {}) {
|
|
|
|
await channels.saveUnprocesseds(_cleanData(arrayOfUnprocessed), {
|
|
|
|
forceSave,
|
2018-07-25 22:02:27 +00:00
|
|
|
});
|
2018-07-27 01:13:56 +00:00
|
|
|
}
|
2018-07-25 22:02:27 +00:00
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function removeUnprocessed(id) {
|
|
|
|
await channels.removeUnprocessed(id);
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function removeAllUnprocessed() {
|
2018-07-27 01:13:56 +00:00
|
|
|
await channels.removeAllUnprocessed();
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function removeAll() {
|
2018-07-27 01:13:56 +00:00
|
|
|
await channels.removeAll();
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-08-08 17:00:33 +00:00
|
|
|
async function cleanupOrphanedAttachments() {
|
|
|
|
await callChannel(CLEANUP_ORPHANED_ATTACHMENTS_KEY);
|
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
async function removeOtherData() {
|
|
|
|
await Promise.all([
|
|
|
|
callChannel(ERASE_SQL_KEY),
|
|
|
|
callChannel(ERASE_ATTACHMENTS_KEY),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function callChannel(name) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ipcRenderer.send(name);
|
|
|
|
ipcRenderer.once(`${name}-done`, (event, error) => {
|
|
|
|
if (error) {
|
|
|
|
return reject(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
setTimeout(
|
|
|
|
() => reject(new Error(`callChannel call to ${name} timed out`)),
|
|
|
|
5000
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions below here return JSON
|
|
|
|
|
|
|
|
async function getLegacyMessagesNeedingUpgrade(
|
2018-07-27 02:19:34 +00:00
|
|
|
limit,
|
|
|
|
{ MessageCollection, maxVersion = MessageType.CURRENT_SCHEMA_VERSION }
|
|
|
|
) {
|
2018-07-25 22:02:27 +00:00
|
|
|
const messages = new MessageCollection();
|
|
|
|
|
|
|
|
await deferredToPromise(
|
|
|
|
messages.fetch({
|
|
|
|
limit,
|
|
|
|
index: {
|
|
|
|
name: 'schemaVersion',
|
2018-07-27 02:19:34 +00:00
|
|
|
upper: maxVersion,
|
2018-07-25 22:02:27 +00:00
|
|
|
excludeUpper: true,
|
|
|
|
order: 'desc',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
const models = messages.models || [];
|
|
|
|
return models.map(model => model.toJSON());
|
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getMessagesNeedingUpgrade(
|
|
|
|
limit,
|
|
|
|
{ maxVersion = MessageType.CURRENT_SCHEMA_VERSION }
|
2018-07-25 22:02:27 +00:00
|
|
|
) {
|
2018-07-27 01:13:56 +00:00
|
|
|
const messages = await channels.getMessagesNeedingUpgrade(limit, {
|
|
|
|
maxVersion,
|
|
|
|
});
|
2018-07-25 22:02:27 +00:00
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
return messages;
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getMessagesWithVisualMediaAttachments(
|
2018-07-25 22:02:27 +00:00
|
|
|
conversationId,
|
2018-07-27 01:13:56 +00:00
|
|
|
{ limit }
|
2018-07-25 22:02:27 +00:00
|
|
|
) {
|
2018-07-27 01:13:56 +00:00
|
|
|
return channels.getMessagesWithVisualMediaAttachments(conversationId, {
|
|
|
|
limit,
|
|
|
|
});
|
2018-07-25 22:02:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 01:13:56 +00:00
|
|
|
async function getMessagesWithFileAttachments(conversationId, { limit }) {
|
|
|
|
return channels.getMessagesWithFileAttachments(conversationId, {
|
|
|
|
limit,
|
|
|
|
});
|
|
|
|
}
|