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

99 lines
3.6 KiB
TypeScript

import strcmp from "../../strcmp/strcmp";
import { Message, ShouldNeverHappenError } from "./data-types";
import Globals from "./globals";
interface MessagesWithMetadata {
messages: Map<string, Message>;
totalCharacters: number;
lastUsed: Date;
}
export default class MessageRAMCache {
// g#guildId/c#channelId -> list of messages
private data = new Map<string, MessagesWithMetadata>();
private totalCharacters: number;
// Removes the least recently used channels until the cache is under the max cached character limit
private cullLeastUsedChannelsIfNeeded(): void {
if (this.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) {
let entries = Array.from(this.data.entries())
.map(([ key, value ]) => ({ channelId: key, value: value }))
.sort((a, b) => b.value.lastUsed.getTime() - a.value.lastUsed.getTime());
while (this.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_CHARACTERS) {
let entry = entries.pop();
if (!entry) throw new ShouldNeverHappenError('could not find an entry to clear');
this.data.delete(entry.channelId);
this.totalCharacters -= entry.value.totalCharacters;
}
}
}
// Removes the oldest messages from the channel until the channel is under the max cached character limit
private trimOldChannelMessagesIfNeeded(guildId: number, channelId: string) {
let id = `g#${guildId}/c#${channelId}`;
let value = this.data.get(id);
if (!value) return;
if (value.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_CHARACTERS) {
let messages = Array.from(value.messages.values()).sort(Message.sortOrder);
while (value.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_CHARACTERS) {
let message = messages.shift();
if (!message) throw new ShouldNeverHappenError('could not find a message to clear');
value.messages.delete(message.id);
value.totalCharacters -= message.text?.length ?? 0;
this.totalCharacters -= message.text?.length ?? 0;
}
}
}
upsertMessages(guildId: number, channelId: string, messages: Message[]) {
let id = `g#${guildId}/c#${channelId}`;
if (!this.data.has(id)) {
this.data.set(id, { messages: new Map<string, Message>(), totalCharacters: 0, lastUsed: new Date() });
}
let value = this.data.get(id);
if (!value) throw new ShouldNeverHappenError('unable to get message map');
for (let message of messages) {
value.messages.set(message.id, message);
value.totalCharacters += message.text?.length ?? 0;
this.totalCharacters += message.text?.length ?? 0;
}
this.cullLeastUsedChannelsIfNeeded();
this.trimOldChannelMessagesIfNeeded(guildId, channelId);
}
deleteMessages(guildId: number, channelId: string, messages: Message[]) {
let id = `g#${guildId}/c#${channelId}`;
if (!this.data.has(id)) {
return;
}
let value = this.data.get(id);
if (!value) throw new ShouldNeverHappenError('unable to get message map');
for (let message of messages) {
let deletedMessage = value.messages.get(message.id);
if (!deletedMessage) continue;
value.messages.delete(message.id);
}
}
fetchRecentMessages(guildId: number, channelId: string, number: number): Message[] | null {
let id = `g#${guildId}/c#${channelId}`;
if (!this.data.has(id)) {
return null;
}
let value = this.data.get(id);
if (!value) throw new ShouldNeverHappenError('unable to get message map');
value.lastUsed = new Date();
let allRecentMessages = Array.from(value.messages.values()).sort(Message.sortOrder);
let start = Math.min(allRecentMessages.length - number, 0);
let result = allRecentMessages.slice(start);
if (result.length === 0) {
return null;
}
return result;
}
}