2020-08-05 18:38:55 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-04-20 21:26:39 +02:00
|
|
|
import {SortedArray, MappedList, ConcatList} from "../../../observable/index.js";
|
2020-09-10 16:40:30 +02:00
|
|
|
import {Disposables} from "../../../utils/Disposables.js";
|
2020-04-20 21:26:39 +02:00
|
|
|
import {Direction} from "./Direction.js";
|
|
|
|
import {TimelineReader} from "./persistence/TimelineReader.js";
|
|
|
|
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
|
2020-09-04 15:28:22 +02:00
|
|
|
import {EventEntry} from "./entries/EventEntry.js";
|
2019-02-27 22:50:08 +01:00
|
|
|
|
2020-04-20 21:26:39 +02:00
|
|
|
export class Timeline {
|
2020-03-21 23:40:40 +01:00
|
|
|
constructor({roomId, storage, closeCallback, fragmentIdComparer, pendingEvents, user}) {
|
2019-02-27 22:50:08 +01:00
|
|
|
this._roomId = roomId;
|
|
|
|
this._storage = storage;
|
|
|
|
this._closeCallback = closeCallback;
|
2019-05-12 20:26:03 +02:00
|
|
|
this._fragmentIdComparer = fragmentIdComparer;
|
2020-09-10 16:40:30 +02:00
|
|
|
this._disposables = new Disposables();
|
2019-07-29 19:53:58 +02:00
|
|
|
this._remoteEntries = new SortedArray((a, b) => a.compare(b));
|
2019-05-19 20:49:46 +02:00
|
|
|
this._timelineReader = new TimelineReader({
|
|
|
|
roomId: this._roomId,
|
|
|
|
storage: this._storage,
|
|
|
|
fragmentIdComparer: this._fragmentIdComparer
|
|
|
|
});
|
2020-09-10 16:40:30 +02:00
|
|
|
this._readerRequest = null;
|
2019-07-29 19:53:58 +02:00
|
|
|
const localEntries = new MappedList(pendingEvents, pe => {
|
|
|
|
return new PendingEventEntry({pendingEvent: pe, user});
|
|
|
|
}, (pee, params) => {
|
|
|
|
pee.notifyUpdate(params);
|
|
|
|
});
|
|
|
|
this._allEntries = new ConcatList(this._remoteEntries, localEntries);
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @package */
|
|
|
|
async load() {
|
2020-09-10 16:40:30 +02:00
|
|
|
// 30 seems to be a good amount to fill the entire screen
|
|
|
|
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(30));
|
|
|
|
try {
|
|
|
|
const entries = await readerRequest.complete();
|
|
|
|
this._remoteEntries.setManySorted(entries);
|
|
|
|
} finally {
|
|
|
|
this._disposables.disposeTracked(readerRequest);
|
|
|
|
}
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|
|
|
|
|
2020-09-04 15:28:22 +02:00
|
|
|
replaceEntries(entries) {
|
|
|
|
for (const entry of entries) {
|
|
|
|
this._remoteEntries.replace(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-07 19:14:53 +02:00
|
|
|
// TODO: should we rather have generic methods for
|
|
|
|
// - adding new entries
|
|
|
|
// - updating existing entries (redaction, relations)
|
2019-02-27 22:50:08 +01:00
|
|
|
/** @package */
|
|
|
|
appendLiveEntries(newEntries) {
|
2020-03-21 10:47:35 +01:00
|
|
|
this._remoteEntries.setManySorted(newEntries);
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|
|
|
|
|
2020-03-21 23:40:40 +01:00
|
|
|
/** @package */
|
|
|
|
addGapEntries(newEntries) {
|
2019-07-29 19:53:58 +02:00
|
|
|
this._remoteEntries.setManySorted(newEntries);
|
2019-03-09 00:41:06 +01:00
|
|
|
}
|
2020-03-21 23:40:40 +01:00
|
|
|
|
2019-06-01 17:39:23 +02:00
|
|
|
// tries to prepend `amount` entries to the `entries` list.
|
2019-03-09 00:41:06 +01:00
|
|
|
async loadAtTop(amount) {
|
2019-07-29 19:58:35 +02:00
|
|
|
const firstEventEntry = this._remoteEntries.array.find(e => !!e.eventType);
|
2019-06-16 17:29:33 +02:00
|
|
|
if (!firstEventEntry) {
|
2019-06-01 17:39:23 +02:00
|
|
|
return;
|
2019-03-09 00:41:06 +01:00
|
|
|
}
|
2020-09-10 16:40:30 +02:00
|
|
|
const readerRequest = this._disposables.track(this._timelineReader.readFrom(
|
2019-06-16 17:29:33 +02:00
|
|
|
firstEventEntry.asEventKey(),
|
2019-06-02 15:15:26 +02:00
|
|
|
Direction.Backward,
|
|
|
|
amount
|
2020-09-10 16:40:30 +02:00
|
|
|
));
|
|
|
|
try {
|
|
|
|
const entries = await readerRequest.complete();
|
|
|
|
this._remoteEntries.setManySorted(entries);
|
|
|
|
} finally {
|
|
|
|
this._disposables.disposeTracked(readerRequest);
|
|
|
|
}
|
2019-03-08 20:03:18 +01:00
|
|
|
}
|
|
|
|
|
2019-02-27 22:50:08 +01:00
|
|
|
/** @public */
|
|
|
|
get entries() {
|
2019-07-29 19:53:58 +02:00
|
|
|
return this._allEntries;
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
close() {
|
2020-05-04 22:21:56 +02:00
|
|
|
if (this._closeCallback) {
|
2020-09-10 16:40:30 +02:00
|
|
|
this._readerRequest?.dispose();
|
|
|
|
this._readerRequest = null;
|
2020-05-04 22:21:56 +02:00
|
|
|
this._closeCallback();
|
|
|
|
this._closeCallback = null;
|
|
|
|
}
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|
2020-09-04 15:28:22 +02:00
|
|
|
|
|
|
|
enableEncryption(decryptEntries) {
|
|
|
|
this._timelineReader.enableEncryption(decryptEntries);
|
|
|
|
}
|
2019-02-27 22:50:08 +01:00
|
|
|
}
|