[FIX] Watermelon throwing "Cannot update a record with pending updates" (#1754)

This commit is contained in:
Diego Mello 2020-03-02 17:12:41 -03:00 committed by GitHub
parent c360c2ca86
commit 3d535196a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 66 deletions

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import equal from 'deep-equal';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
@ -42,7 +43,24 @@ const Content = React.memo((props) => {
{content} {content}
</View> </View>
); );
}, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => {
if (prevProps.isTemp !== nextProps.isTemp) {
return false;
}
if (prevProps.msg !== nextProps.msg) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (!equal(prevProps.mentions, nextProps.mentions)) {
return false;
}
if (!equal(prevProps.channels, nextProps.channels)) {
return false;
}
return true;
});
Content.propTypes = { Content.propTypes = {
isTemp: PropTypes.bool, isTemp: PropTypes.bool,

View File

@ -5,7 +5,7 @@ import database from '../database';
import log from '../../utils/log'; import log from '../../utils/log';
import random from '../../utils/random'; import random from '../../utils/random';
const changeMessageStatus = async(id, tmid, status) => { const changeMessageStatus = async(id, tmid, status, message) => {
const db = database.active; const db = database.active;
const msgCollection = db.collections.get('messages'); const msgCollection = db.collections.get('messages');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.collections.get('thread_messages');
@ -14,6 +14,8 @@ const changeMessageStatus = async(id, tmid, status) => {
successBatch.push( successBatch.push(
messageRecord.prepareUpdate((m) => { messageRecord.prepareUpdate((m) => {
m.status = status; m.status = status;
m.mentions = message.mentions;
m.channels = message.channels;
}) })
); );
@ -22,6 +24,8 @@ const changeMessageStatus = async(id, tmid, status) => {
successBatch.push( successBatch.push(
threadMessageRecord.prepareUpdate((tm) => { threadMessageRecord.prepareUpdate((tm) => {
tm.status = status; tm.status = status;
tm.mentions = message.mentions;
tm.channels = message.channels;
}) })
); );
} }
@ -42,12 +46,12 @@ export async function sendMessageCall(message) {
try { try {
const sdk = this.shareSDK || this.sdk; const sdk = this.shareSDK || this.sdk;
// RC 0.60.0 // RC 0.60.0
await sdk.post('chat.sendMessage', { const result = await sdk.post('chat.sendMessage', {
message: { message: {
_id, rid, msg, tmid _id, rid, msg, tmid
} }
}); });
await changeMessageStatus(_id, tmid, messagesStatus.SENT); await changeMessageStatus(_id, tmid, messagesStatus.SENT, result.message);
} catch (e) { } catch (e) {
await changeMessageStatus(_id, tmid, messagesStatus.ERROR); await changeMessageStatus(_id, tmid, messagesStatus.ERROR);
} }

View File

@ -11,10 +11,17 @@ import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actio
import debounce from '../../../utils/debounce'; import debounce from '../../../utils/debounce';
import RocketChat from '../../rocketchat'; import RocketChat from '../../rocketchat';
const WINDOW_TIME = 1000;
export default class RoomSubscription { export default class RoomSubscription {
constructor(rid) { constructor(rid) {
this.rid = rid; this.rid = rid;
this.isAlive = true; this.isAlive = true;
this.timer = null;
this.queue = {};
this.messagesBatch = {};
this.threadsBatch = {};
this.threadMessagesBatch = {};
} }
subscribe = async() => { subscribe = async() => {
@ -49,6 +56,9 @@ export default class RoomSubscription {
this.removeListener(this.notifyRoomListener); this.removeListener(this.notifyRoomListener);
this.removeListener(this.messageReceivedListener); this.removeListener(this.messageReceivedListener);
reduxStore.dispatch(clearUserTyping()); reduxStore.dispatch(clearUserTyping());
if (this.timer) {
clearTimeout(this.timer);
}
} }
removeListener = async(promise) => { removeListener = async(promise) => {
@ -131,15 +141,13 @@ export default class RoomSubscription {
RocketChat.readMessages(this.rid, lastOpen); RocketChat.readMessages(this.rid, lastOpen);
}, 300); }, 300);
handleMessageReceived = protectedFunction((ddpMessage) => { updateMessage = message => (
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0])); new Promise(async(resolve) => {
const lastOpen = new Date(); if (this.rid !== message.rid) {
if (this.rid !== message.rid) { return;
return; }
}
InteractionManager.runAfterInteractions(async() => {
const db = database.active; const db = database.active;
const batch = [];
const msgCollection = db.collections.get('messages'); const msgCollection = db.collections.get('messages');
const threadsCollection = db.collections.get('threads'); const threadsCollection = db.collections.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.collections.get('thread_messages');
@ -154,22 +162,17 @@ export default class RoomSubscription {
// Do nothing // Do nothing
} }
if (messageRecord) { if (messageRecord) {
try { const update = messageRecord.prepareUpdate((m) => {
const update = messageRecord.prepareUpdate((m) => { Object.assign(m, message);
Object.assign(m, message); });
}); this._messagesBatch[message._id] = update;
batch.push(update);
} catch (e) {
console.log(e);
}
} else { } else {
batch.push( const create = msgCollection.prepareCreate(protectedFunction((m) => {
msgCollection.prepareCreate(protectedFunction((m) => { m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); m.subscription.id = this.rid;
m.subscription.id = this.rid; Object.assign(m, message);
Object.assign(m, message); }));
})) this._messagesBatch[message._id] = create;
);
} }
// Create or update thread // Create or update thread
@ -181,19 +184,17 @@ export default class RoomSubscription {
} }
if (threadRecord) { if (threadRecord) {
batch.push( const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
threadRecord.prepareUpdate(protectedFunction((t) => { Object.assign(t, message);
Object.assign(t, message); }));
})) this._threadsBatch[message._id] = updateThread;
);
} else { } else {
batch.push( const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
threadsCollection.prepareCreate(protectedFunction((t) => { t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema); t.subscription.id = this.rid;
t.subscription.id = this.rid; Object.assign(t, message);
Object.assign(t, message); }));
})) this._threadsBatch[message._id] = createThread;
);
} }
} }
@ -206,35 +207,75 @@ export default class RoomSubscription {
} }
if (threadMessageRecord) { if (threadMessageRecord) {
batch.push( const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
threadMessageRecord.prepareUpdate(protectedFunction((tm) => { Object.assign(tm, message);
Object.assign(tm, message); tm.rid = message.tmid;
tm.rid = message.tmid; delete tm.tmid;
delete tm.tmid; }));
})) this._threadMessagesBatch[message._id] = updateThreadMessage;
);
} else { } else {
batch.push( const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
threadMessagesCollection.prepareCreate(protectedFunction((tm) => { tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema); Object.assign(tm, message);
Object.assign(tm, message); tm.subscription.id = this.rid;
tm.subscription.id = this.rid; tm.rid = message.tmid;
tm.rid = message.tmid; delete tm.tmid;
delete tm.tmid; }));
})) this._threadMessagesBatch[message._id] = createThreadMessage;
);
} }
} }
this.read(lastOpen); return resolve();
})
)
try { handleMessageReceived = (ddpMessage) => {
await db.action(async() => { if (!this.timer) {
await db.batch(...batch); this.timer = setTimeout(async() => {
}); // copy variables values to local and clean them
} catch (e) { const _lastOpen = this.lastOpen;
log(e); const _queue = Object.keys(this.queue).map(key => this.queue[key]);
} this._messagesBatch = this.messagesBatch;
}); this._threadsBatch = this.threadsBatch;
}); this._threadMessagesBatch = this.threadMessagesBatch;
this.queue = {};
this.messagesBatch = {};
this.threadsBatch = {};
this.threadMessagesBatch = {};
this.timer = null;
for (let i = 0; i < _queue.length; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await this.updateMessage(_queue[i]);
} catch (e) {
log(e);
}
}
try {
const db = database.active;
await db.action(async() => {
await db.batch(
...Object.values(this._messagesBatch),
...Object.values(this._threadsBatch),
...Object.values(this._threadMessagesBatch)
);
});
this.read(_lastOpen);
} catch (e) {
log(e);
}
// Clean local variables
this._messagesBatch = {};
this._threadsBatch = {};
this._threadMessagesBatch = {};
}, WINDOW_TIME);
}
this.lastOpen = new Date();
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
this.queue[message._id] = message;
};
} }

View File

@ -74,17 +74,29 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
// Update // Update
msgsToUpdate = msgsToUpdate.map((message) => { msgsToUpdate = msgsToUpdate.map((message) => {
const newMessage = update.find(m => m._id === message.id); const newMessage = update.find(m => m._id === message.id);
if (message._hasPendingUpdate) {
console.log(message);
return;
}
return message.prepareUpdate(protectedFunction((m) => { return message.prepareUpdate(protectedFunction((m) => {
Object.assign(m, newMessage); Object.assign(m, newMessage);
})); }));
}); });
threadsToUpdate = threadsToUpdate.map((thread) => { threadsToUpdate = threadsToUpdate.map((thread) => {
if (thread._hasPendingUpdate) {
console.log(thread);
return;
}
const newThread = allThreads.find(t => t._id === thread.id); const newThread = allThreads.find(t => t._id === thread.id);
return thread.prepareUpdate(protectedFunction((t) => { return thread.prepareUpdate(protectedFunction((t) => {
Object.assign(t, newThread); Object.assign(t, newThread);
})); }));
}); });
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => { threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
if (threadMessage._hasPendingUpdate) {
console.log(threadMessage);
return;
}
const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id); const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
return threadMessage.prepareUpdate(protectedFunction((tm) => { return threadMessage.prepareUpdate(protectedFunction((tm) => {
Object.assign(tm, newThreadMessage); Object.assign(tm, newThreadMessage);

View File

@ -5,6 +5,19 @@ import { isIOS } from './deviceInfo';
export const animateNextTransition = debounce(() => { export const animateNextTransition = debounce(() => {
if (isIOS) { if (isIOS) {
LayoutAnimation.easeInEaseOut(); LayoutAnimation.configureNext({
duration: 200,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity
},
update: {
type: LayoutAnimation.Types.easeInEaseOut
},
delete: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity
}
});
} }
}, 200, true); }, 200, true);