[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 { Text, View } from 'react-native';
import PropTypes from 'prop-types';
import equal from 'deep-equal';
import I18n from '../../i18n';
import styles from './styles';
@ -42,7 +43,24 @@ const Content = React.memo((props) => {
{content}
</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 = {
isTemp: PropTypes.bool,

View File

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

View File

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

View File

@ -5,6 +5,19 @@ import { isIOS } from './deviceInfo';
export const animateNextTransition = debounce(() => {
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);