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

View file

@ -22,7 +22,7 @@ import {TimelineReader} from "./persistence/TimelineReader.js";
import {PendingEventEntry} from "./entries/PendingEventEntry.js"; import {PendingEventEntry} from "./entries/PendingEventEntry.js";
import {RoomMember} from "../members/RoomMember.js"; import {RoomMember} from "../members/RoomMember.js";
import {PowerLevels} from "./PowerLevels.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"; import {REDACTION_TYPE} from "../common.js";
export class Timeline { export class Timeline {
@ -120,42 +120,36 @@ export class Timeline {
// so if we are redacting a relation, we can pass the redaction // so if we are redacting a relation, we can pass the redaction
// to the relation target and the removal of the relation can // to the relation target and the removal of the relation can
// be taken into account for local echo. // be taken into account for local echo.
let redactingRelation; let redactingEntry;
if (pe.eventType === REDACTION_TYPE) { if (pe.eventType === REDACTION_TYPE) {
if (pe.relatedEventId) { redactingEntry = await this._getOrLoadEntry(pe.relatedTxnId, 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;
}
}
}
} }
const pee = new PendingEventEntry({ const pee = new PendingEventEntry({
pendingEvent: pe, member: this._ownMember, pendingEvent: pe, member: this._ownMember,
clock: this._clock, redactingRelation clock: this._clock, redactingEntry
}); });
this._applyAndEmitLocalRelationChange(pee, target => target.addLocalRelation(pee)); this._applyAndEmitLocalRelationChange(pee, target => target.addLocalRelation(pee));
return pee; return pee;
} }
_applyAndEmitLocalRelationChange(pee, updater) { _applyAndEmitLocalRelationChange(pee, updater) {
// this is the contract of findAndUpdate, used in _findAndUpdateRelatedEntry
const updateOrFalse = e => { const updateOrFalse = e => {
const params = updater(e); const params = updater(e);
return params ? params : false; 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; let found = false;
const {relatedTxnId} = pee.pendingEvent;
// first, look in local entries based on txn id // first, look in local entries based on txn id
if (relatedTxnId) { if (relatedTxnId) {
found = this._localEntries.findAndUpdate( found = this._localEntries.findAndUpdate(
@ -164,18 +158,9 @@ export class Timeline {
); );
} }
// if not found here, look in remote entries based on event id // if not found here, look in remote entries based on event id
if (!found && pee.relatedEventId) { if (!found && relatedEventId) {
this._remoteEntries.findAndUpdate( this._remoteEntries.findAndUpdate(
e => e.id === pee.relatedEventId, e => e.id === 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,
updateOrFalse updateOrFalse
); );
} }
@ -233,8 +218,8 @@ export class Timeline {
relationTarget.addLocalRelation(pee); relationTarget.addLocalRelation(pee);
} }
} }
if (pee.redactingRelation) { if (pee.redactingEntry) {
const eventId = pee.redactingRelation.event_id; const eventId = pee.redactingEntry.relatedEventId;
const relationTarget = entries.find(e => e.id === eventId); const relationTarget = entries.find(e => e.id === eventId);
if (relationTarget) { if (relationTarget) {
relationTarget.addLocalRelation(pee); 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) { getByEventId(eventId) {
for (let i = 0; i < this._remoteEntries.length; i += 1) { for (let i = 0; i < this._remoteEntries.length; i += 1) {
const entry = this._remoteEntries.get(i); const entry = this._remoteEntries.get(i);

View file

@ -16,9 +16,11 @@ limitations under the License.
import {BaseEntry} from "./BaseEntry.js"; import {BaseEntry} from "./BaseEntry.js";
import {REDACTION_TYPE} from "../../common.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"; 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 { export class BaseEventEntry extends BaseEntry {
constructor(fragmentIdComparer) { constructor(fragmentIdComparer) {
super(fragmentIdComparer); super(fragmentIdComparer);
@ -59,9 +61,9 @@ export class BaseEventEntry extends BaseEntry {
return "isRedacted"; return "isRedacted";
} }
} else { } else {
const relation = entry.ownOrRedactedRelation; const relationEntry = entry.redactingEntry || entry;
if (relation && relation.event_id === this.id) { if (relationEntry.isRelationForId(this.id)) {
if (relation.rel_type === ANNOTATION_RELATION_TYPE) { if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE) {
if (!this._pendingAnnotations) { if (!this._pendingAnnotations) {
this._pendingAnnotations = new PendingAnnotations(); this._pendingAnnotations = new PendingAnnotations();
} }
@ -87,9 +89,9 @@ export class BaseEventEntry extends BaseEntry {
} }
} }
} else { } else {
const relation = entry.ownOrRedactedRelation; const relationEntry = entry.redactingEntry || entry;
if (relation && relation.event_id === this.id) { if (relationEntry.isRelationForId(this.id)) {
if (relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) { if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
this._pendingAnnotations.remove(entry); this._pendingAnnotations.remove(entry);
if (this._pendingAnnotations.isEmpty) { if (this._pendingAnnotations.isEmpty) {
this._pendingAnnotations = null; this._pendingAnnotations = null;
@ -121,6 +123,14 @@ export class BaseEventEntry extends BaseEntry {
return createAnnotation(this.id, key); return createAnnotation(this.id, key);
} }
isRelationForId(id) {
return id && this.relation?.event_id === id;
}
get relation() {
return getRelationFromContent(this.content);
}
get pendingAnnotations() { get pendingAnnotations() {
return this._pendingAnnotations?.aggregatedAnnotations; return this._pendingAnnotations?.aggregatedAnnotations;
} }

View file

@ -16,16 +16,15 @@ limitations under the License.
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js"; import {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
import {BaseEventEntry} from "./BaseEventEntry.js"; import {BaseEventEntry} from "./BaseEventEntry.js";
import {getRelationFromContent} from "../relations.js";
export class PendingEventEntry extends BaseEventEntry { export class PendingEventEntry extends BaseEventEntry {
constructor({pendingEvent, member, clock, redactingRelation}) { constructor({pendingEvent, member, clock, redactingEntry}) {
super(null); super(null);
this._pendingEvent = pendingEvent; this._pendingEvent = pendingEvent;
/** @type {RoomMember} */ /** @type {RoomMember} */
this._member = member; this._member = member;
this._clock = clock; this._clock = clock;
this._redactingRelation = redactingRelation; this._redactingEntry = redactingEntry;
} }
get fragmentId() { 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() { get relatedEventId() {
return this._pendingEvent.relatedEventId; return this._pendingEvent.relatedEventId;
} }
get redactingRelation() { get redactingEntry() {
return this._redactingRelation; return this._redactingEntry;
}
/**
* 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);
} }
} }