cordis/client/webapp/message-ram-cache.ts

135 lines
4.6 KiB
TypeScript

import Globals from './globals';
import { Message, ShouldNeverHappenError } from './data-types';
// TODO: make this non-static
export default class RecentMessageRAMCache {
private data = new Map<string, { messages: Message[], size: number, hasFirstMessage: boolean, lastUsed: Date }>(); // (serverId, channelId) -> { messages, size, hasFirstMessage, lastUsed }
private size = 0;
clear(): void {
this.data.clear();
this.size = 0;
}
_cullIfNeeded(): void { // TODO: Test this
if (this.size > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_SIZE) {
let entries = Array.from(this.data.entries())
.map(([ key, value ]) => { return { id: key, value: value }})
.sort((a, b) => b.value.lastUsed.getTime() - a.value.lastUsed.getTime());
while (this.size > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_SIZE) {
let entry = entries.pop();
if (entry === undefined) throw new ShouldNeverHappenError('No entry in the array but the message cache still has a size...');
this.data.delete(entry.id);
this.size -= entry.value.size;
}
}
}
_cullChannelIfNeeded(serverId: string, channelId: string): void { // TODO: test this
let id = `s#${serverId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
while (value.size > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) {
value.hasFirstMessage = false;
let message = value.messages.shift();
if (!message) return;
value.size -= message.text?.length ?? 0;
this.size -= message.text?.length ?? 0;
}
}
// @param messages may be modified in addNewMessage due to pass-by-reference fun
putRecentMessages(serverId: string, channelId: string, messages: Message[]): void {
let size = 0;
for (let message of messages) {
size += message.text?.length ?? 0;
}
if (size > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) return;
let id = `s#${serverId}/c#${channelId}`;
this.data.set(id, {
messages: messages,
size: size,
hasFirstMessage: true, // will be false if this was ever channel-culled
lastUsed: new Date()
});
this.size += size;
this._cullIfNeeded();
}
addNewMessage(serverId: string, message: Message): void {
let channelId = message.channel.id;
let id = `s#${serverId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
value.messages.push(message);
value.size += message.text?.length ?? 0;
this.size += message.text?.length ?? 0;
this._cullChannelIfNeeded(serverId, channelId);
this._cullIfNeeded();
}
updateMessage(serverId: string, oldMessage: Message, newMessage: Message): void {
let channelId = oldMessage.channel.id;
let id = `s#${serverId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
let taggedIndex = value.messages.findIndex(cachedMessage => cachedMessage.id == oldMessage.id);
let [ oldCachedMessage ] = value.messages.splice(taggedIndex, 1, newMessage);
value.size -= oldCachedMessage.text?.length ?? 0;
value.size += newMessage.text?.length ?? 0;
this.size -= oldCachedMessage.text?.length ?? 0;
this.size += newMessage.text?.length ?? 0;
this._cullChannelIfNeeded(serverId, channelId);
this._cullIfNeeded();
}
deleteMessage(serverId: string, message: Message): void {
let channelId = message.channel.id;
let id = `s#${serverId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
let taggedIndex = value.messages.findIndex(cachedMessage => cachedMessage.id == message.id);
let [ oldCachedMessage ] = value.messages.splice(taggedIndex, 1);
value.size -= oldCachedMessage.text?.length ?? 0;
this.size -= oldCachedMessage.text?.length ?? 0;
// No need to cull since we are always shrinking
}
dropChannel(serverId: string, channelId: string): void {
let id = `s#${serverId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
this.size -= value.size;
this.data.delete(id);
}
getRecentMessages(serverId: string, channelId: string, number: number): Message[] | null {
let id = `s#${serverId}/c#${channelId}`;
if (!this.data.has(id)) {
//LOG.silly(`recent message cache miss on ${id} requesting ${number}`);
return null; // not in the cache
}
let value = this.data.get(id);
if (!value) throw new ShouldNeverHappenError('javascript is not on a single thread >:|');
if (!value.hasFirstMessage && value.messages.length < number) {
//LOG.silly(`recent message cache incomplete on ${id} requesting ${number}`, { value });
return null; // requesting older messages than we have
}
value.lastUsed = new Date();
return value.messages.slice(-number);
}
}