99 lines
3.6 KiB
TypeScript
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;
|
|
}
|
|
}
|