This repository has been archived on 2022-08-19. You can view files and clone it, but cannot push or open issues or pull requests.
hydrogen-web/src/matrix/room/timeline/persistence/TimelineReader.js

166 lines
6.6 KiB
JavaScript

/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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 {directionalConcat, directionalAppend} from "./common.js";
import {Direction} from "../Direction";
import {EventEntry} from "../entries/EventEntry.js";
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
class ReaderRequest {
constructor(fn, log) {
this.decryptRequest = null;
this._promise = fn(this, log);
}
complete() {
return this._promise;
}
dispose() {
if (this.decryptRequest) {
this.decryptRequest.dispose();
this.decryptRequest = null;
}
}
}
/**
* Raw because it doesn't do decryption and in the future it should not read relations either.
* It is just about reading entries and following fragment links
*/
async function readRawTimelineEntriesWithTxn(roomId, eventKey, direction, amount, fragmentIdComparer, txn) {
let entries = [];
const timelineStore = txn.timelineEvents;
const fragmentStore = txn.timelineFragments;
while (entries.length < amount && eventKey) {
let eventsWithinFragment;
if (direction.isForward) {
// TODO: should we pass amount - entries.length here?
eventsWithinFragment = await timelineStore.eventsAfter(roomId, eventKey, amount);
} else {
eventsWithinFragment = await timelineStore.eventsBefore(roomId, eventKey, amount);
}
console.log('readRawTimelineEntriesWithTxn eventsWithinFragment', eventsWithinFragment)
let eventEntries = eventsWithinFragment.map(e => new EventEntry(e, fragmentIdComparer));
entries = directionalConcat(entries, eventEntries, direction);
// prepend or append eventsWithinFragment to entries, and wrap them in EventEntry
if (entries.length < amount) {
const fragment = await fragmentStore.get(roomId, eventKey.fragmentId);
// TODO: why does the first fragment not need to be added? (the next *is* added below)
// it looks like this would be fine when loading in the sync island
// (as the live fragment should be added already) but not for permalinks when we support them
//
// fragmentIdComparer.addFragment(fragment);
let fragmentEntry = new FragmentBoundaryEntry(fragment, direction.isBackward, fragmentIdComparer);
// append or prepend fragmentEntry, reuse func from GapWriter?
directionalAppend(entries, fragmentEntry, direction);
// only continue loading if the fragment boundary can't be backfilled
if (!fragmentEntry.token && fragmentEntry.hasLinkedFragment) {
const nextFragment = await fragmentStore.get(roomId, fragmentEntry.linkedFragmentId);
fragmentIdComparer.add(nextFragment);
const nextFragmentEntry = new FragmentBoundaryEntry(nextFragment, direction.isForward, fragmentIdComparer);
directionalAppend(entries, nextFragmentEntry, direction);
eventKey = nextFragmentEntry.asEventKey();
} else {
eventKey = null;
}
}
}
return entries;
}
export class TimelineReader {
constructor({roomId, storage, fragmentIdComparer}) {
this._roomId = roomId;
this._storage = storage;
this._fragmentIdComparer = fragmentIdComparer;
this._decryptEntries = null;
}
enableEncryption(decryptEntries) {
this._decryptEntries = decryptEntries;
}
get readTxnStores() {
const stores = [
this._storage.storeNames.timelineEvents,
this._storage.storeNames.timelineFragments,
];
if (this._decryptEntries) {
stores.push(this._storage.storeNames.inboundGroupSessions);
}
return stores;
}
readFrom(eventKey, direction, amount, log) {
return new ReaderRequest(async (r, log) => {
const txn = await this._storage.readTxn(this.readTxnStores);
return await this._readFrom(eventKey, direction, amount, r, txn, log);
}, log);
}
readFromEnd(amount, existingTxn = null, log) {
return new ReaderRequest(async (r, log) => {
const txn = existingTxn || await this._storage.readTxn(this.readTxnStores);
const liveFragment = await txn.timelineFragments.liveFragment(this._roomId);
let entries;
// room hasn't been synced yet
if (!liveFragment) {
entries = [];
} else {
this._fragmentIdComparer.add(liveFragment);
const liveFragmentEntry = FragmentBoundaryEntry.end(liveFragment, this._fragmentIdComparer);
const eventKey = liveFragmentEntry.asEventKey();
entries = await this._readFrom(eventKey, Direction.Backward, amount, r, txn, log);
entries.unshift(liveFragmentEntry);
}
return entries;
}, log);
}
async readById(id, log) {
let stores = [this._storage.storeNames.timelineEvents];
if (this._decryptEntries) {
stores.push(this._storage.storeNames.inboundGroupSessions);
}
const txn = await this._storage.readTxn(stores); // todo: can we just use this.readTxnStores here? probably
const storageEntry = await txn.timelineEvents.getByEventId(this._roomId, id);
if (storageEntry) {
const entry = new EventEntry(storageEntry, this._fragmentIdComparer);
if (this._decryptEntries) {
const request = this._decryptEntries([entry], txn, log);
await request.complete();
}
return entry;
}
}
async _readFrom(eventKey, direction, amount, r, txn, log) {
const entries = await readRawTimelineEntriesWithTxn(this._roomId, eventKey, direction, amount, this._fragmentIdComparer, txn);
if (this._decryptEntries) {
r.decryptRequest = this._decryptEntries(entries, txn, log);
try {
await r.decryptRequest.complete();
} finally {
r.decryptRequest = null;
}
}
return entries;
}
}