diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 62bb67bd..ed9889b1 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -333,6 +333,7 @@ export class Sync { storeNames.roomState, storeNames.roomMembers, storeNames.timelineEvents, + storeNames.timelineRelations, storeNames.timelineFragments, storeNames.pendingEvents, storeNames.userIdentities, diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 9269404a..8df281fd 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -259,6 +259,7 @@ export class BaseRoom extends EventEmitter { const txn = await this._storage.readWriteTxn([ this._storage.storeNames.pendingEvents, this._storage.storeNames.timelineEvents, + this._storage.storeNames.timelineRelations, this._storage.storeNames.timelineFragments, ]); let extraGapFillChanges; diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js index bd477cbd..4d10ef65 100644 --- a/src/matrix/storage/common.js +++ b/src/matrix/storage/common.js @@ -22,6 +22,7 @@ export const STORE_NAMES = Object.freeze([ "invites", "roomMembers", "timelineEvents", + "timelineRelations", "timelineFragments", "pendingEvents", "userIdentities", diff --git a/src/matrix/storage/idb/Transaction.js b/src/matrix/storage/idb/Transaction.js index a2041d31..bdcc45e3 100644 --- a/src/matrix/storage/idb/Transaction.js +++ b/src/matrix/storage/idb/Transaction.js @@ -21,6 +21,7 @@ import {SessionStore} from "./stores/SessionStore.js"; import {RoomSummaryStore} from "./stores/RoomSummaryStore.js"; import {InviteStore} from "./stores/InviteStore.js"; import {TimelineEventStore} from "./stores/TimelineEventStore.js"; +import {TimelineRelationStore} from "./stores/TimelineRelationStore.js"; import {RoomStateStore} from "./stores/RoomStateStore.js"; import {RoomMemberStore} from "./stores/RoomMemberStore.js"; import {TimelineFragmentStore} from "./stores/TimelineFragmentStore.js"; @@ -82,6 +83,10 @@ export class Transaction { return this._store("timelineEvents", idbStore => new TimelineEventStore(idbStore)); } + get timelineRelations() { + return this._store("timelineRelations", idbStore => new TimelineRelationStore(idbStore)); + } + get roomState() { return this._store("roomState", idbStore => new RoomStateStore(idbStore)); } diff --git a/src/matrix/storage/idb/schema.js b/src/matrix/storage/idb/schema.js index 3af31d8d..256b6732 100644 --- a/src/matrix/storage/idb/schema.js +++ b/src/matrix/storage/idb/schema.js @@ -16,6 +16,7 @@ export const schema = [ createInviteStore, createArchivedRoomSummaryStore, migrateOperationScopeIndex, + createTimelineRelationsStore, ]; // TODO: how to deal with git merge conflicts of this array? @@ -135,4 +136,9 @@ async function migrateOperationScopeIndex(db, txn) { txn.abort(); console.error("could not migrate operations", err.stack); } +} + +//v10 +function createTimelineRelationsStore(db) { + db.createObjectStore("timelineRelations", {keyPath: ""}); } \ No newline at end of file diff --git a/src/matrix/storage/idb/stores/TimelineRelationStore.js b/src/matrix/storage/idb/stores/TimelineRelationStore.js new file mode 100644 index 00000000..504693f9 --- /dev/null +++ b/src/matrix/storage/idb/stores/TimelineRelationStore.js @@ -0,0 +1,62 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import {MIN_UNICODE, MAX_UNICODE} from "./common.js"; + +function encodeKey(roomId, targetEventId, relType, sourceEventId) { + return `${roomId}|${targetEventId}|${relType}|${sourceEventId}`; +} + +function decodeKey(key) { + const [roomId, targetEventId, relType, sourceEventId] = key.split("|"); + return {roomId, targetEventId, relType, sourceEventId}; +} + +export class TimelineRelationStore { + constructor(store) { + this._store = store; + } + + add(roomId, targetEventId, relType, sourceEventId) { + return this._store.add(encodeKey(roomId, targetEventId, relType, sourceEventId)); + } + + remove(roomId, targetEventId, relType, sourceEventId) { + return this._store.delete(encodeKey(roomId, targetEventId, relType, sourceEventId)); + } + + removeAllForTarget(roomId, targetId) { + const range = this._store.IDBKeyRange.bound( + encodeKey(roomId, targetId, MIN_UNICODE, MIN_UNICODE), + encodeKey(roomId, targetId, MAX_UNICODE, MAX_UNICODE), + true, + true + ); + return this._store.delete(range); + } + + async getForTargetAndType(roomId, targetId, relType) { + // exclude both keys as they are theoretical min and max, + // but we should't have a match for just the room id, or room id with max + const range = this._store.IDBKeyRange.bound( + encodeKey(roomId, targetId, relType, MIN_UNICODE), + encodeKey(roomId, targetId, relType, MAX_UNICODE), + true, + true + ); + const keys = await this._store.selectAll(range); + return keys.map(decodeKey); + } +}