From 145e5c6b5583a55cd697c5e78dad040991aed301 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 8 Oct 2019 09:36:15 -0300 Subject: [PATCH] [FIX] Watermelon batches (#1277) --- app/containers/EmojiPicker/index.js | 26 ++-- app/containers/MessageErrorActions.js | 71 ++++++++-- app/containers/message/Message.js | 2 +- app/containers/message/RepliedThread.js | 8 +- app/lib/methods/readMessages.js | 9 +- app/lib/methods/sendMessage.js | 180 +++++++++++++++++------- app/lib/methods/subscriptions/room.js | 62 +++++--- app/lib/methods/subscriptions/rooms.js | 22 +-- app/lib/rocketchat.js | 28 ++-- app/views/RoomView/index.js | 1 + app/views/ShareListView/index.js | 8 +- 11 files changed, 286 insertions(+), 131 deletions(-) diff --git a/app/containers/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js index 1b0a7c172..05ec129c1 100644 --- a/app/containers/EmojiPicker/index.js +++ b/app/containers/EmojiPicker/index.js @@ -91,22 +91,24 @@ class EmojiPicker extends Component { _addFrequentlyUsed = protectedFunction(async(emoji) => { const db = database.active; const freqEmojiCollection = db.collections.get('frequently_used_emojis'); + let freqEmojiRecord; + try { + freqEmojiRecord = await freqEmojiCollection.find(emoji.content); + } catch (error) { + // Do nothing + } + await db.action(async() => { - try { - const freqEmojiRecord = await freqEmojiCollection.find(emoji.content); + if (freqEmojiRecord) { await freqEmojiRecord.update((f) => { f.count += 1; }); - } catch (error) { - try { - await freqEmojiCollection.create((f) => { - f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema); - Object.assign(f, emoji); - f.count = 1; - }); - } catch (e) { - // Do nothing - } + } else { + await freqEmojiCollection.create((f) => { + f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema); + Object.assign(f, emoji); + f.count = 1; + }); } }); }) diff --git a/app/containers/MessageErrorActions.js b/app/containers/MessageErrorActions.js index d42cd0330..d106da635 100644 --- a/app/containers/MessageErrorActions.js +++ b/app/containers/MessageErrorActions.js @@ -6,11 +6,13 @@ import RocketChat from '../lib/rocketchat'; import database from '../lib/database'; import protectedFunction from '../lib/methods/helpers/protectedFunction'; import I18n from '../i18n'; +import log from '../utils/log'; class MessageErrorActions extends React.Component { static propTypes = { actionsHide: PropTypes.func.isRequired, - message: PropTypes.object + message: PropTypes.object, + tmid: PropTypes.string }; // eslint-disable-next-line react/sort-comp @@ -27,17 +29,66 @@ class MessageErrorActions extends React.Component { } handleResend = protectedFunction(async() => { - const { message } = this.props; - await RocketChat.resendMessage(message); + const { message, tmid } = this.props; + await RocketChat.resendMessage(message, tmid); }); - handleDelete = protectedFunction(async() => { - const { message } = this.props; - const db = database.active; - await db.action(async() => { - await message.destroyPermanently(); - }); - }) + handleDelete = async() => { + try { + const { message, tmid } = this.props; + const db = database.active; + const deleteBatch = []; + const msgCollection = db.collections.get('messages'); + const threadCollection = db.collections.get('threads'); + + // Delete the object (it can be Message or ThreadMessage instance) + deleteBatch.push(message.prepareDestroyPermanently()); + + // If it's a thread, we find and delete the whole tree, if necessary + if (tmid) { + try { + const msg = await msgCollection.find(message.id); + deleteBatch.push(msg.prepareDestroyPermanently()); + } catch (error) { + // Do nothing: message not found + } + + try { + // Find the thread header and update it + const msg = await msgCollection.find(tmid); + if (msg.tcount <= 1) { + deleteBatch.push( + msg.prepareUpdate((m) => { + m.tcount = null; + m.tlm = null; + }) + ); + + try { + // If the whole thread was removed, delete the thread + const thread = await threadCollection.find(tmid); + deleteBatch.push(thread.prepareDestroyPermanently()); + } catch (error) { + // Do nothing: thread not found + } + } else { + deleteBatch.push( + msg.prepareUpdate((m) => { + m.tcount -= 1; + }) + ); + } + } catch (error) { + // Do nothing: message not found + } + } + await db.action(async() => { + await db.batch(...deleteBatch); + }); + } catch (e) { + log(e); + } + } showActionSheet = () => { ActionSheet.showActionSheetWithOptions({ diff --git a/app/containers/message/Message.js b/app/containers/message/Message.js index e26040d5a..8e49b3b3a 100644 --- a/app/containers/message/Message.js +++ b/app/containers/message/Message.js @@ -52,7 +52,7 @@ MessageInner.displayName = 'MessageInner'; const Message = React.memo((props) => { if (props.isThreadReply || props.isThreadSequential || props.isInfo) { - const thread = props.isThreadReply ? : null; + const thread = props.isThreadReply ? : null; return ( {thread} diff --git a/app/containers/message/RepliedThread.js b/app/containers/message/RepliedThread.js index 7a6f17b7f..7e43dbd11 100644 --- a/app/containers/message/RepliedThread.js +++ b/app/containers/message/RepliedThread.js @@ -9,9 +9,9 @@ import DisclosureIndicator from '../DisclosureIndicator'; import styles from './styles'; const RepliedThread = React.memo(({ - tmid, tmsg, isHeader, isTemp, fetchThreadName, id + tmid, tmsg, isHeader, fetchThreadName, id }) => { - if (!tmid || !isHeader || isTemp) { + if (!tmid || !isHeader) { return null; } @@ -40,9 +40,6 @@ const RepliedThread = React.memo(({ if (prevProps.isHeader !== nextProps.isHeader) { return false; } - if (prevProps.isTemp !== nextProps.isTemp) { - return false; - } return true; }); @@ -51,7 +48,6 @@ RepliedThread.propTypes = { tmsg: PropTypes.string, id: PropTypes.string, isHeader: PropTypes.bool, - isTemp: PropTypes.bool, fetchThreadName: PropTypes.func }; RepliedThread.displayName = 'MessageRepliedThread'; diff --git a/app/lib/methods/readMessages.js b/app/lib/methods/readMessages.js index 9e61cfc44..552cd1e0e 100644 --- a/app/lib/methods/readMessages.js +++ b/app/lib/methods/readMessages.js @@ -3,12 +3,14 @@ import log from '../../utils/log'; export default async function readMessages(rid, lastOpen) { try { - // RC 0.61.0 - const data = await this.sdk.post('subscriptions.read', { rid }); const db = database.active; + const subscription = await db.collections.get('subscriptions').find(rid); + + // RC 0.61.0 + await this.sdk.post('subscriptions.read', { rid }); + await db.action(async() => { try { - const subscription = await db.collections.get('subscriptions').find(rid); await subscription.update((s) => { s.open = true; s.alert = false; @@ -22,7 +24,6 @@ export default async function readMessages(rid, lastOpen) { // Do nothing } }); - return data; } catch (e) { log(e); } diff --git a/app/lib/methods/sendMessage.js b/app/lib/methods/sendMessage.js index 5b7f051ca..bd383bd5d 100644 --- a/app/lib/methods/sendMessage.js +++ b/app/lib/methods/sendMessage.js @@ -5,81 +5,155 @@ import database from '../database'; import log from '../../utils/log'; import random from '../../utils/random'; -export const getMessage = async(rid, msg = '', tmid, user) => { - const _id = random(17); - const { id, username } = user; - try { - const db = database.active; - const msgCollection = db.collections.get('messages'); - let message; - await db.action(async() => { - message = await msgCollection.create((m) => { - m._raw = sanitizedRaw({ id: _id }, msgCollection.schema); - m.subscription.id = rid; - m.msg = msg; - m.tmid = tmid; - m.ts = new Date(); - m._updatedAt = new Date(); - m.status = messagesStatus.TEMP; - m.u = { - _id: id || '1', - username - }; - }); - }); - return message; - } catch (error) { - console.warn('getMessage', error); - } -}; - export async function sendMessageCall(message) { const { id: _id, subscription: { id: rid }, msg, tmid } = message; - // RC 0.60.0 - const data = await this.sdk.post('chat.sendMessage', { - message: { - _id, rid, msg, tmid + try { + // RC 0.60.0 + await this.sdk.post('chat.sendMessage', { + message: { + _id, rid, msg, tmid + } + }); + } catch (e) { + const db = database.active; + const msgCollection = db.collections.get('messages'); + const threadMessagesCollection = db.collections.get('thread_messages'); + const errorBatch = []; + const messageRecord = await msgCollection.find(_id); + errorBatch.push( + messageRecord.prepareUpdate((m) => { + m.status = messagesStatus.ERROR; + }) + ); + + if (tmid) { + const threadMessageRecord = await threadMessagesCollection.find(_id); + errorBatch.push( + threadMessageRecord.prepareUpdate((tm) => { + tm.status = messagesStatus.ERROR; + }) + ); } - }); - return data; + + await db.action(async() => { + await db.batch(...errorBatch); + }); + } } export default async function(rid, msg, tmid, user) { try { const db = database.active; - const subsCollections = db.collections.get('subscriptions'); - const message = await getMessage(rid, msg, tmid, user); - if (!message) { - return; + const subsCollection = db.collections.get('subscriptions'); + const msgCollection = db.collections.get('messages'); + const threadCollection = db.collections.get('threads'); + const threadMessagesCollection = db.collections.get('thread_messages'); + const messageId = random(17); + const batch = []; + const message = { + id: messageId, subscription: { id: rid }, msg, tmid + }; + const messageDate = new Date(); + let tMessageRecord; + + // If it's replying to a thread + if (tmid) { + try { + // Find thread message header in Messages collection + tMessageRecord = await msgCollection.find(tmid); + batch.push( + tMessageRecord.prepareUpdate((m) => { + m.tlm = messageDate; + m.tcount += 1; + }) + ); + + try { + // Find thread message header in Threads collection + await threadCollection.find(tmid); + } catch (error) { + // If there's no record, create one + batch.push( + threadCollection.prepareCreate((tm) => { + tm._raw = sanitizedRaw({ id: tmid }, threadCollection.schema); + tm.subscription.id = rid; + tm.tmid = tmid; + tm.msg = tMessageRecord.msg; + tm.ts = tMessageRecord.ts; + tm._updatedAt = messageDate; + tm.status = messagesStatus.SENT; // Original message was sent already + tm.u = tMessageRecord.u; + }) + ); + } + + // Create the message sent in ThreadMessages collection + batch.push( + threadMessagesCollection.prepareCreate((tm) => { + tm._raw = sanitizedRaw({ id: messageId }, threadMessagesCollection.schema); + tm.subscription.id = rid; + tm.rid = tmid; + tm.msg = msg; + tm.ts = messageDate; + tm._updatedAt = messageDate; + tm.status = messagesStatus.TEMP; + tm.u = { + _id: user.id || '1', + username: user.username + }; + }) + ); + } catch (e) { + log(e); + } } + // Create the message sent in Messages collection + batch.push( + msgCollection.prepareCreate((m) => { + m._raw = sanitizedRaw({ id: messageId }, msgCollection.schema); + m.subscription.id = rid; + m.msg = msg; + m.ts = messageDate; + m._updatedAt = messageDate; + m.status = messagesStatus.TEMP; + m.u = { + _id: user.id || '1', + username: user.username + }; + if (tmid) { + m.tmid = tmid; + m.tlm = messageDate; + m.tmsg = tMessageRecord.msg; + } + }) + ); + try { - const room = await subsCollections.find(rid); - await db.action(async() => { - await room.update((r) => { - r.draftMessage = null; - }); - }); + const room = await subsCollection.find(rid); + if (room.draftMessage) { + batch.push( + room.prepareUpdate((r) => { + r.draftMessage = null; + }) + ); + } } catch (e) { // Do nothing } try { - await sendMessageCall.call(this, message); await db.action(async() => { - await message.update((m) => { - m.status = messagesStatus.SENT; - }); + await db.batch(...batch); }); } catch (e) { - await db.action(async() => { - await message.update((m) => { - m.status = messagesStatus.ERROR; - }); - }); + log(e); + return; } + + await sendMessageCall.call(this, message); } catch (e) { log(e); } diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index 1a3de160b..2dab116d5 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -8,6 +8,7 @@ import buildMessage from '../helpers/buildMessage'; import database from '../../database'; import reduxStore from '../../createStore'; import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping'; +import debounce from '../../../utils/debounce'; const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom'))); const removeListener = listener => listener.stop(); @@ -85,6 +86,10 @@ export default function subscribeRoom({ rid }) { } }); + const read = debounce((lastOpen) => { + this.readMessages(rid, lastOpen); + }, 300); + const handleMessageReceived = protectedFunction((ddpMessage) => { const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0])); const lastOpen = new Date(); @@ -94,20 +99,26 @@ export default function subscribeRoom({ rid }) { InteractionManager.runAfterInteractions(async() => { const db = database.active; const batch = []; - const subCollection = db.collections.get('subscriptions'); const msgCollection = db.collections.get('messages'); const threadsCollection = db.collections.get('threads'); const threadMessagesCollection = db.collections.get('thread_messages'); + let messageRecord; + let threadRecord; + let threadMessageRecord; // Create or update message try { - const messageRecord = await msgCollection.find(message._id); - batch.push( - messageRecord.prepareUpdate((m) => { - Object.assign(m, message); - }) - ); + messageRecord = await msgCollection.find(message._id); } catch (error) { + // Do nothing + } + if (messageRecord) { + batch.push( + messageRecord.prepareUpdate(protectedFunction((m) => { + Object.assign(m, message); + })) + ); + } else { batch.push( msgCollection.prepareCreate(protectedFunction((m) => { m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); @@ -120,13 +131,18 @@ export default function subscribeRoom({ rid }) { // Create or update thread if (message.tlm) { try { - const threadRecord = await threadsCollection.find(message._id); - batch.push( - threadRecord.prepareUpdate((t) => { - Object.assign(t, message); - }) - ); + threadRecord = await threadsCollection.find(message._id); } catch (error) { + // Do nothing + } + + if (threadRecord) { + batch.push( + threadRecord.prepareUpdate(protectedFunction((t) => { + Object.assign(t, message); + })) + ); + } else { batch.push( threadsCollection.prepareCreate(protectedFunction((t) => { t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema); @@ -140,15 +156,20 @@ export default function subscribeRoom({ rid }) { // Create or update thread message if (message.tmid) { try { - const threadMessageRecord = await threadMessagesCollection.find(message._id); + threadMessageRecord = await threadMessagesCollection.find(message._id); + } catch (error) { + // Do nothing + } + + if (threadMessageRecord) { batch.push( - threadMessageRecord.prepareUpdate((tm) => { + threadMessageRecord.prepareUpdate(protectedFunction((tm) => { Object.assign(tm, message); tm.rid = message.tmid; delete tm.tmid; - }) + })) ); - } catch (error) { + } else { batch.push( threadMessagesCollection.prepareCreate(protectedFunction((tm) => { tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema); @@ -161,12 +182,7 @@ export default function subscribeRoom({ rid }) { } } - try { - await subCollection.find(rid); - this.readMessages(rid, lastOpen); - } catch (e) { - console.log('Subscription not found. We probably subscribed to a not joined channel. No need to mark as read.'); - } + read(lastOpen); try { await db.action(async() => { diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js index c6b940c9e..f279f8b30 100644 --- a/app/lib/methods/subscriptions/rooms.js +++ b/app/lib/methods/subscriptions/rooms.js @@ -66,10 +66,10 @@ const createOrUpdateSubscription = async(subscription, room) => { } catch (error) { try { await db.action(async() => { - await roomsCollection.create((r) => { + await roomsCollection.create(protectedFunction((r) => { r._raw = sanitizedRaw({ id: room._id }, roomsCollection.schema); Object.assign(r, room); - }); + })); }); } catch (e) { // Do nothing @@ -96,19 +96,25 @@ const createOrUpdateSubscription = async(subscription, room) => { const tmp = merge(subscription, room); await db.action(async() => { + let sub; try { - const sub = await subCollection.find(tmp.rid); - await sub.update((s) => { - Object.assign(s, tmp); - }); + sub = await subCollection.find(tmp.rid); } catch (error) { - await subCollection.create((s) => { + // Do nothing + } + + if (sub) { + await sub.update(protectedFunction((s) => { + Object.assign(s, tmp); + })); + } else { + await subCollection.create(protectedFunction((s) => { s._raw = sanitizedRaw({ id: tmp.rid }, subCollection.schema); Object.assign(s, tmp); if (s.roomUpdatedAt) { s.roomUpdatedAt = new Date(); } - }); + })); } }); } catch (e) { diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index e62548457..a9007b71f 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -39,7 +39,7 @@ import loadMessagesForRoom from './methods/loadMessagesForRoom'; import loadMissedMessages from './methods/loadMissedMessages'; import loadThreadMessages from './methods/loadThreadMessages'; -import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage'; +import sendMessage, { sendMessageCall } from './methods/sendMessage'; import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage'; import callJitsi from './methods/callJitsi'; @@ -427,11 +427,10 @@ const RocketChat = { loadMissedMessages, loadMessagesForRoom, loadThreadMessages, - getMessage, sendMessage, getRooms, readMessages, - async resendMessage(message) { + async resendMessage(message, tmid) { const db = database.active; try { await db.action(async() => { @@ -439,17 +438,20 @@ const RocketChat = { m.status = messagesStatus.TEMP; }); }); - await sendMessageCall.call(this, message); - } catch (error) { - try { - await db.action(async() => { - await message.update((m) => { - m.status = messagesStatus.ERROR; - }); - }); - } catch (e) { - log(e); + let m = { + id: message.id, + msg: message.msg, + subscription: { id: message.subscription.id } + }; + if (tmid) { + m = { + ...m, + tmid + }; } + await sendMessageCall.call(this, m); + } catch (e) { + log(e); } }, diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 31e9f40ac..6975599d1 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -775,6 +775,7 @@ class RoomView extends React.Component { } {showErrorActions ? ( diff --git a/app/views/ShareListView/index.js b/app/views/ShareListView/index.js index f419ffc60..5914d8048 100644 --- a/app/views/ShareListView/index.js +++ b/app/views/ShareListView/index.js @@ -198,7 +198,13 @@ class ShareListView extends React.Component { const serversCollection = serversDB.collections.get('servers'); this.servers = await serversCollection.query().fetch(); this.chats = this.data.slice(0, LIMIT); - const serverInfo = await serversCollection.find(server); + let serverInfo = {}; + try { + serverInfo = await serversCollection.find(server); + } catch (error) { + // Do nothing + } + const canUploadFileResult = canUploadFile(fileInfo || fileData, serverInfo); this.internalSetState({