mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
WIP: Oops
This commit is contained in:
parent
a905a3d077
commit
515aaa8e77
196
src/CachingClient.ts
Normal file
196
src/CachingClient.ts
Normal file
@ -0,0 +1,196 @@
|
||||
import { MatrixClient, UserID } from "matrix-bot-sdk";
|
||||
|
||||
/**
|
||||
* A caching layer on top of MatrixClient.
|
||||
*
|
||||
* This layer is designed to speed up Mjölnir and reduce the load on
|
||||
* the homeserver by caching critical data.
|
||||
*
|
||||
* # Caching policy
|
||||
*
|
||||
* As an instance of Many-Mjölnir can end up instantiating thousands of
|
||||
* instances of `Mjolnir`, we do not wish to cache *everything*. Rather,
|
||||
* we cache only data that we know we'll be using repeatedly.
|
||||
*/
|
||||
export class CachingClient {
|
||||
// The user id. Initialized by `start()`.
|
||||
private _userId?: string;
|
||||
private _localpart?: string;
|
||||
private _displayName?: string | null;
|
||||
private _domain?: string;
|
||||
|
||||
// The contents of account data.
|
||||
private _accountData: Map<String, AccountDataCache> = new Map();
|
||||
private _joinedRooms: Cache<Set<string>>;
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The implementation of the MatrixClient
|
||||
*/
|
||||
public readonly uncached: MatrixClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Initialize this client.
|
||||
*
|
||||
* This MUST be called once before using the `CachingClient`.
|
||||
*
|
||||
* Do not forget to call `stop()` at the end.
|
||||
*/
|
||||
public async start() {
|
||||
this.uncached.on("account_data", event => {
|
||||
if ("type" in event && typeof event.type === "string") {
|
||||
this._accountData.set(event.type, event);
|
||||
}
|
||||
});
|
||||
|
||||
await this.uncached.start();
|
||||
if (this._userId) {
|
||||
throw new TypeError("Already initialized");
|
||||
}
|
||||
this._userId = await this.uncached.getUserId();
|
||||
let userID = new UserID(this._userId);
|
||||
this._localpart = userID.localpart;
|
||||
this._domain = userID.domain;
|
||||
let profile = await this.uncached.getUserProfile(this._userId);
|
||||
this._displayName = profile?.['displayname'] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the client.
|
||||
*/
|
||||
public stop() {
|
||||
this.uncached.stop();
|
||||
for (let cache of this._accountData.values()) {
|
||||
cache.unregister();
|
||||
}
|
||||
this._accountData.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user id for this client.
|
||||
*/
|
||||
public get userId(): string {
|
||||
return this._userId!;
|
||||
}
|
||||
public get localpart(): string {
|
||||
return this._localpart!;
|
||||
}
|
||||
public get domain(): string {
|
||||
return this._domain!;
|
||||
}
|
||||
public get displayName(): string | null {
|
||||
return this._displayName || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for knowing about account data of a specific kind.
|
||||
* @param type
|
||||
*/
|
||||
public async accountData(type: string): Promise<WritableCache<any>> {
|
||||
let cache = this._accountData.get(type);
|
||||
if (!cache) {
|
||||
let newCache = new AccountDataCache(this.uncached, type);
|
||||
await newCache.init();
|
||||
this._accountData.set(type, newCache);
|
||||
cache = newCache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
public joinedRooms(): Cache<Set<string>> {
|
||||
if (!this._joinedRooms) {
|
||||
this._joinedRooms = new JoinedRoomsCache(this.uncached);
|
||||
}
|
||||
return this._joinedRooms;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cached value.
|
||||
*
|
||||
* Call `get()` to obtain the latest value.
|
||||
*/
|
||||
export abstract class Cache<T> {
|
||||
_value: T | null;
|
||||
public get(): Readonly<T> | null {
|
||||
return this._value;
|
||||
}
|
||||
cache(value: T) {
|
||||
this._value = value;
|
||||
}
|
||||
abstract unregister(): void;
|
||||
}
|
||||
|
||||
export abstract class WritableCache<T> extends Cache<T> {
|
||||
abstract send(value: T): Promise<void>;
|
||||
public async set(value: T): Promise<T | null> {
|
||||
let previous = this.get();
|
||||
await this.send(value);
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cache specialized to store the list of currently joined rooms.
|
||||
*
|
||||
* `get()` returns a `Set` of room ids for all the rooms currently joined.
|
||||
*/
|
||||
class JoinedRoomsCache extends Cache<Set<string>> {
|
||||
constructor(private uncached: MatrixClient) {
|
||||
super();
|
||||
}
|
||||
async init() {
|
||||
let data = await this.uncached.getJoinedRooms();
|
||||
this.cache(new Set(data));
|
||||
await this.uncached.on("room.join", this.onJoin);
|
||||
await this.uncached.on("room.leave", this.onLeave);
|
||||
}
|
||||
onJoin = (roomId: string) => {
|
||||
this._value?.add(roomId);
|
||||
}
|
||||
onLeave = (roomId: string) => {
|
||||
this._value?.delete(roomId);
|
||||
}
|
||||
unregister() {
|
||||
this.uncached.removeListener("room.join", this.onJoin);
|
||||
this.uncached.removeListener("room.join", this.onLeave);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cache for account data.
|
||||
*
|
||||
* One instance of `AccountDataCache` for each `type` of account data watched.
|
||||
*/
|
||||
class AccountDataCache extends WritableCache<any> {
|
||||
public async send(value: any): Promise<void> {
|
||||
await this.uncached.setAccountData(this.type, value);
|
||||
}
|
||||
|
||||
constructor(private readonly uncached: MatrixClient, public readonly type: string) {
|
||||
super();
|
||||
|
||||
}
|
||||
async init() {
|
||||
let data;
|
||||
try {
|
||||
data = await this.uncached.getAccountData(this.type);
|
||||
} catch (ex) {
|
||||
if (ex.statusCode != 404) {
|
||||
throw ex;
|
||||
}
|
||||
// Otherwise, this is a "not found" exception, which means that the account doesn't contain any such data.
|
||||
}
|
||||
this.cache(data);
|
||||
this.uncached.on("account_data", this.watch);
|
||||
}
|
||||
watch = (event: any) => {
|
||||
if ("type" in event && event.type === this.type) {
|
||||
this.cache(event);
|
||||
}
|
||||
}
|
||||
unregister() {
|
||||
this.uncached.removeListener("account_data", this.watch);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user