more local echo fixes for redacting a reaction + cleanup

This commit is contained in:
Bruno Windels 2021-06-16 12:46:44 +02:00
parent 94635a18e0
commit bbcf0d2572
4 changed files with 78 additions and 63 deletions

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {getRelationFromContent} from "./relations.js";
export class PendingAnnotations {
constructor() {
this.aggregatedAnnotations = new Map();
@ -25,7 +23,7 @@ export class PendingAnnotations {
/** adds either a pending annotation entry, or a remote annotation entry with a pending redaction */
add(entry) {
const {key} = entry.ownOrRedactedRelation;
const {key} = (entry.redactingEntry || entry).relation;
if (!key) {
return;
}
@ -42,7 +40,7 @@ export class PendingAnnotations {
return;
}
this._entries.splice(idx, 1);
const {key} = entry.ownOrRedactedRelation;
const {key} = (entry.redactingEntry || entry).relation;
let count = this.aggregatedAnnotations.get(key);
if (count !== undefined) {
const addend = entry.isRedaction ? 1 : -1;
@ -56,8 +54,7 @@ export class PendingAnnotations {
findForKey(key) {
return this._entries.find(e => {
const relation = getRelationFromContent(e.content);
if (relation && relation.key === key) {
if (e.relation?.key === key) {
return e;
}
});
@ -65,8 +62,7 @@ export class PendingAnnotations {
findRedactionForKey(key) {
return this._entries.find(e => {
const relation = e.redactingRelation;
if (relation && relation.key === key) {
if (e.redactingEntry?.relation?.key === key) {
return e;
}
});

View file

@ -22,7 +22,7 @@ import {TimelineReader} from "./persistence/TimelineReader.js";
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
import {RoomMember} from "../members/RoomMember.js";
import {PowerLevels} from "./PowerLevels.js";
import {getRelation, getRelationFromContent, ANNOTATION_RELATION_TYPE} from "./relations.js";
import {getRelation, ANNOTATION_RELATION_TYPE} from "./relations.js";
import {REDACTION_TYPE} from "../common.js";
export class Timeline {
@ -120,42 +120,36 @@ export class Timeline {
// so if we are redacting a relation, we can pass the redaction
// to the relation target and the removal of the relation can
// be taken into account for local echo.
let redactingRelation;
let redactingEntry;
if (pe.eventType === REDACTION_TYPE) {
if (pe.relatedEventId) {
const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineEvents,
]);
const redactionTargetEntry = await txn.timelineEvents.getByEventId(this._roomId, pe.relatedEventId);
if (redactionTargetEntry) {
redactingRelation = getRelation(redactionTargetEntry.event);
}
} else if (pe.relatedTxnId) {
// also look for redacting relation in pending events, in case the target is already being sent
for (const p of this._localEntries) {
if (p.id === pe.relatedTxnId) {
redactingRelation = getRelationFromContent(p.content);
break;
}
}
}
redactingEntry = await this._getOrLoadEntry(pe.relatedTxnId, pe.relatedEventId);
}
const pee = new PendingEventEntry({
pendingEvent: pe, member: this._ownMember,
clock: this._clock, redactingRelation
clock: this._clock, redactingEntry
});
this._applyAndEmitLocalRelationChange(pee, target => target.addLocalRelation(pee));
return pee;
}
_applyAndEmitLocalRelationChange(pee, updater) {
// this is the contract of findAndUpdate, used in _findAndUpdateRelatedEntry
const updateOrFalse = e => {
const params = updater(e);
return params ? params : false;
};
this._findAndUpdateRelatedEntry(pee.pendingEvent.relatedTxnId, pee.relatedEventId, updateOrFalse);
// also look for a relation target to update with this redaction
const {redactingEntry} = pee;
if (redactingEntry) {
// redactingEntry might be a PendingEventEntry or an EventEntry, so don't assume pendingEvent
const relatedTxnId = redactingEntry.pendingEvent?.relatedTxnId;
this._findAndUpdateRelatedEntry(relatedTxnId, redactingEntry.relatedEventId, updateOrFalse);
}
}
_findAndUpdateRelatedEntry(relatedTxnId, relatedEventId, updateOrFalse) {
let found = false;
const {relatedTxnId} = pee.pendingEvent;
// first, look in local entries based on txn id
if (relatedTxnId) {
found = this._localEntries.findAndUpdate(
@ -164,18 +158,9 @@ export class Timeline {
);
}
// if not found here, look in remote entries based on event id
if (!found && pee.relatedEventId) {
if (!found && relatedEventId) {
this._remoteEntries.findAndUpdate(
e => e.id === pee.relatedEventId,
updateOrFalse
);
}
// also look for a relation target to update with this redaction
if (pee.redactingRelation) {
const eventId = pee.redactingRelation.event_id;
// TODO: also support reacting to pending entries
this._remoteEntries.findAndUpdate(
e => e.id === eventId,
e => e.id === relatedEventId,
updateOrFalse
);
}
@ -233,8 +218,8 @@ export class Timeline {
relationTarget.addLocalRelation(pee);
}
}
if (pee.redactingRelation) {
const eventId = pee.redactingRelation.event_id;
if (pee.redactingEntry) {
const eventId = pee.redactingEntry.relatedEventId;
const relationTarget = entries.find(e => e.id === eventId);
if (relationTarget) {
relationTarget.addLocalRelation(pee);
@ -278,6 +263,32 @@ export class Timeline {
}
}
async _getOrLoadEntry(txnId, eventId) {
if (txnId) {
// also look for redacting relation in pending events, in case the target is already being sent
for (const p of this._localEntries) {
if (p.id === txnId) {
return p;
}
}
}
if (eventId) {
const loadedEntry = this.getByEventId(eventId);
if (loadedEntry) {
return loadedEntry;
} else {
const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineEvents,
]);
const redactionTargetEntry = await txn.timelineEvents.getByEventId(this._roomId, eventId);
if (redactionTargetEntry) {
return new EventEntry(redactionTargetEntry, this._fragmentIdComparer);
}
}
}
return null;
}
getByEventId(eventId) {
for (let i = 0; i < this._remoteEntries.length; i += 1) {
const entry = this._remoteEntries.get(i);

View file

@ -16,9 +16,11 @@ limitations under the License.
import {BaseEntry} from "./BaseEntry.js";
import {REDACTION_TYPE} from "../../common.js";
import {createAnnotation, ANNOTATION_RELATION_TYPE} from "../relations.js";
import {createAnnotation, ANNOTATION_RELATION_TYPE, getRelationFromContent} from "../relations.js";
import {PendingAnnotations} from "../PendingAnnotations.js";
/** Deals mainly with local echo for relations and redactions,
* so it is shared between PendingEventEntry and EventEntry */
export class BaseEventEntry extends BaseEntry {
constructor(fragmentIdComparer) {
super(fragmentIdComparer);
@ -59,9 +61,9 @@ export class BaseEventEntry extends BaseEntry {
return "isRedacted";
}
} else {
const relation = entry.ownOrRedactedRelation;
if (relation && relation.event_id === this.id) {
if (relation.rel_type === ANNOTATION_RELATION_TYPE) {
const relationEntry = entry.redactingEntry || entry;
if (relationEntry.isRelationForId(this.id)) {
if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE) {
if (!this._pendingAnnotations) {
this._pendingAnnotations = new PendingAnnotations();
}
@ -87,9 +89,9 @@ export class BaseEventEntry extends BaseEntry {
}
}
} else {
const relation = entry.ownOrRedactedRelation;
if (relation && relation.event_id === this.id) {
if (relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
const relationEntry = entry.redactingEntry || entry;
if (relationEntry.isRelationForId(this.id)) {
if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
this._pendingAnnotations.remove(entry);
if (this._pendingAnnotations.isEmpty) {
this._pendingAnnotations = null;
@ -121,6 +123,14 @@ export class BaseEventEntry extends BaseEntry {
return createAnnotation(this.id, key);
}
isRelationForId(id) {
return id && this.relation?.event_id === id;
}
get relation() {
return getRelationFromContent(this.content);
}
get pendingAnnotations() {
return this._pendingAnnotations?.aggregatedAnnotations;
}

View file

@ -16,16 +16,15 @@ limitations under the License.
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
import {BaseEventEntry} from "./BaseEventEntry.js";
import {getRelationFromContent} from "../relations.js";
export class PendingEventEntry extends BaseEventEntry {
constructor({pendingEvent, member, clock, redactingRelation}) {
constructor({pendingEvent, member, clock, redactingEntry}) {
super(null);
this._pendingEvent = pendingEvent;
/** @type {RoomMember} */
this._member = member;
this._clock = clock;
this._redactingRelation = redactingRelation;
this._redactingEntry = redactingEntry;
}
get fragmentId() {
@ -84,19 +83,18 @@ export class PendingEventEntry extends BaseEventEntry {
}
isRelationForId(id) {
if (id && id === this._pendingEvent.relatedTxnId) {
return true;
}
return super.isRelationForId(id);
}
get relatedEventId() {
return this._pendingEvent.relatedEventId;
}
get redactingRelation() {
return this._redactingRelation;
}
/**
* returns either the relationship on this entry,
* or the relationship this entry is redacting.
*
* Useful while aggregating relations for local echo. */
get ownOrRedactedRelation() {
return this.redactingRelation || getRelationFromContent(this._pendingEvent.content);
get redactingEntry() {
return this._redactingEntry;
}
}