dbcache no longer is a static singleton

This commit is contained in:
Michael Peters 2021-11-07 17:04:04 -06:00
parent 9a17a1d97f
commit cf39e3d29c
4 changed files with 136 additions and 159 deletions

View File

@ -16,8 +16,6 @@ import { Message, Member, Channel, Changes, ConnectionInfo, CacheServerData, Ser
import DBCache from './db-cache';
import ResourceRAMCache from './resource-ram-cache';
import RecentMessageRAMCache from './message-ram-cache';
import { channel } from 'diagnostics_channel';
import Q from './q-module';
// Events:
// 'connected' function() called when connected to the server
@ -73,6 +71,8 @@ interface FetchCachedAndVerifyProps<ClientType, ServerType> {
}
export default class ClientController extends EventEmitter {
private dbCache: DBCache;
public id: string;
public memberId: string;
public url: string;
@ -96,9 +96,11 @@ export default class ClientController extends EventEmitter {
public resourceCallbacks: Map<string, ((err: any, resourceBuff: Buffer | null) => Promise<void> | void)[]>;
public dedupedCallbacks: Map<string, (() => Promise<void> | void)[]>;
constructor(config: ServerConfig) {
constructor(dbCache: DBCache, config: ServerConfig) {
super();
this.dbCache = dbCache;
// TODO: fetch these from the cache when they are needed rather than storing them in memory (especially private key)
let publicKey = typeof config.publicKey === 'string' ? crypto.createPublicKey(config.publicKey) : config.publicKey;
let privateKey = typeof config.privateKey === 'string' ? crypto.createPrivateKey(config.privateKey) : config.privateKey;
@ -157,7 +159,7 @@ export default class ClientController extends EventEmitter {
await this.ensureMembers();
await this.ensureChannels();
let message = Message.fromDBData(dataMessage, this.members, this.channels);
await DBCache.upsertServerMessages(this.id, message.channel.id, [ message ]);
await this.dbCache.upsertServerMessages(this.id, message.channel.id, [ message ]);
LOG.info(message.toString());
this._recentMessages.addNewMessage(this.id, message);
this.emit('new-message', message);
@ -185,7 +187,7 @@ export default class ClientController extends EventEmitter {
this.emit('added-channels', [ channel ]);
});
this.socket.on('update-server', async (serverMeta) => {
await DBCache.updateServer(this.id, serverMeta);
await this.dbCache.updateServer(this.id, serverMeta);
this.emit('update-server', serverMeta);
});
}
@ -195,38 +197,38 @@ export default class ClientController extends EventEmitter {
for (let member of members) {
this.members.set(member.id, member);
}
await DBCache.updateServerMembers(this.id, Array.from(this.members.values()));
await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values()));
});
this.on('updated-members', async (data: { oldMember: Member, newMember: Member }[]) => {
for (const { oldMember, newMember } of data) {
this.members.set(newMember.id, newMember);
}
await DBCache.updateServerMembers(this.id, Array.from(this.members.values()));
await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values()));
});
this.on('deleted-members', async (members: Member[]) => {
for (let member of members) {
this.members.delete(member.id);
}
await DBCache.updateServerMembers(this.id, Array.from(this.members.values()));
await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values()));
});
this.on('added-channels', async (channels: Channel[]) => {
for (let channel of channels) {
this.channels.set(channel.id, channel);
}
await DBCache.updateServerChannels(this.id, Array.from(this.channels.values()));
await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values()));
});
this.on('updated-channels', async (data: { oldChannel: Channel, newChannel: Channel }[]) => {
for (const { oldChannel, newChannel } of data) {
this.channels.set(newChannel.id, newChannel);
}
await DBCache.updateServerChannels(this.id, Array.from(this.channels.values()));
await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values()));
});
this.on('deleted-channels', async (channels: Channel[]) => {
for (let channel of channels) {
this.channels.delete(channel.id);
}
await DBCache.updateServerChannels(this.id, Array.from(this.channels.values()));
await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values()));
});
this.on('added-messages', async (channel: Channel, addedAfter: Map<string, Message>, addedBefore: Map<string, Message>) => {
@ -239,19 +241,19 @@ export default class ClientController extends EventEmitter {
// messages for the first time
// Alternatively, just store the date in the message and use order-by
this._recentMessages.dropChannel(this.id, channel.id);
await DBCache.upsertServerMessages(this.id, channel.id, Array.from(addedAfter.values()));
await this.dbCache.upsertServerMessages(this.id, channel.id, Array.from(addedAfter.values()));
});
this.on('updated-messages', async (channel: Channel, data: { oldMessage: Message, newMessage: Message }[]) => {
for (let { oldMessage, newMessage } of data) {
this._recentMessages.updateMessage(this.id, oldMessage, newMessage);
}
await DBCache.upsertServerMessages(this.id, channel.id, data.map(change => change.newMessage));
await this.dbCache.upsertServerMessages(this.id, channel.id, data.map(change => change.newMessage));
});
this.on('deleted-messages', async (_channel: Channel, messages: Message[]) => {
for (let message of messages) {
this._recentMessages.deleteMessage(this.id, message);
}
await DBCache.deleteServerMessages(this.id, messages.map(message => message.id));
await this.dbCache.deleteServerMessages(this.id, messages.map(message => message.id));
});
}
@ -427,7 +429,7 @@ export default class ClientController extends EventEmitter {
return;
}
this.memberId = memberId;
DBCache.updateServerMemberId(this.id, this.memberId);
this.dbCache.updateServerMemberId(this.id, this.memberId);
this.isVerified = true;
LOG.info(`s#${this.id} client verified as u#${this.memberId}`);
resolve();
@ -457,7 +459,7 @@ export default class ClientController extends EventEmitter {
return;
}
this.memberId = memberId;
DBCache.updateServerMemberId(this.id, this.memberId);
this.dbCache.updateServerMemberId(this.id, this.memberId);
this.isVerified = true;
LOG.info(`verified at server#${this.id} as u#${this.memberId}`);
resolve();
@ -534,10 +536,10 @@ export default class ClientController extends EventEmitter {
}
let metadata = await this._fetchCachedAndVerify<CacheServerData, ServerMetaData>({
serverFunc: async () => { return ServerMetaData.fromServerDBData(await this._queryServer('fetch-server')); },
cacheFunc: async () => { return await DBCache.getServer(this.id); },
cacheFunc: async () => { return await this.dbCache.getServer(this.id); },
cacheUpdateFunc: async (cacheData: CacheServerData | null, serverData: ServerMetaData) => {
if (!isDifferent(cacheData, serverData)) return false;
await DBCache.updateServer(this.id, serverData);
await this.dbCache.updateServer(this.id, serverData);
return true;
},
updateEventName: 'update-server',
@ -575,7 +577,7 @@ export default class ClientController extends EventEmitter {
LOG.warn('Unable to find self in members', this.members);
}
} else {
let cacheMembers = await DBCache.getMembers(this.id);
let cacheMembers = await this.dbCache.getMembers(this.id);
if (cacheMembers) {
let member = cacheMembers.find(m => m.id == this.memberId);
if (member) {
@ -599,7 +601,7 @@ export default class ClientController extends EventEmitter {
let dataMembers = (await this._queryServer('fetch-members')) as any[];
return dataMembers.map((dataMember: any) => Member.fromDBData(dataMember));
},
cacheFunc: async () => { return await DBCache.getMembers(this.id); },
cacheFunc: async () => { return await this.dbCache.getMembers(this.id); },
cacheUpdateFunc: async (cacheData: Member[] | null, serverData: Member[]) => {
function equal(cacheMember: Member, serverMember: Member) {
return (
@ -619,7 +621,7 @@ export default class ClientController extends EventEmitter {
return false;
}
await DBCache.updateServerMembers(this.id, serverData);
await this.dbCache.updateServerMembers(this.id, serverData);
this._updateCachedMembers(serverData);
if (changes.deleted.length > 0) {
@ -648,7 +650,7 @@ export default class ClientController extends EventEmitter {
let dataChannels = (await this._queryServer('fetch-channels')) as any[];
return dataChannels.map((dataChannel: any) => Channel.fromDBData(dataChannel));
},
cacheFunc: async () => { return await DBCache.getChannels(this.id); },
cacheFunc: async () => { return await this.dbCache.getChannels(this.id); },
cacheUpdateFunc: async (cacheData: Channel[] | null, serverData: Channel[]) => {
function equal(cacheChannel: Channel, serverChannel: Channel) {
return cacheChannel.id == serverChannel.id &&
@ -749,7 +751,7 @@ export default class ClientController extends EventEmitter {
// if all of the cache data was invalid, it is likely that it needs to be cleared
// this typically happens when the server got a lot of new messages since the cache
// was last updated
await DBCache.clearServerMessages(this.id, deletedMessages[0].channel.id);
await this.dbCache.clearServerMessages(this.id, deletedMessages[0].channel.id);
} else if (deletedMessages.length > 0) {
// Messages from the cache that come on the far side of the request are marked as deleted
// so they are deleted from the UI. However, they should not be removed from the cache
@ -773,7 +775,7 @@ export default class ClientController extends EventEmitter {
}
//LOG.debug('skipping ' + i + ' deleted messages on the cache side -> deleting ' + cacheDeletedMessages.length + ' cache messages instead of ' + deletedMessages.length);
if (cacheDeletedMessages.length > 0) {
await DBCache.deleteServerMessages(this.id, cacheDeletedMessages.map(m => m.id));
await this.dbCache.deleteServerMessages(this.id, cacheDeletedMessages.map(m => m.id));
}
}
}
@ -800,7 +802,7 @@ export default class ClientController extends EventEmitter {
return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels));
},
cacheFunc: async () => {
return await DBCache.getMessagesRecent(this.id, channelId, number, this.members, this.channels);
return await this.dbCache.getMessagesRecent(this.id, channelId, number, this.members, this.channels);
},
cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => {
return await this.updateMessageCache(channelId, null, null, cacheData, serverData);
@ -820,7 +822,7 @@ export default class ClientController extends EventEmitter {
return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels));
},
cacheFunc: async () => {
return await DBCache.getMessagesBefore(this.id, channelId, messageId, number, this.members, this.channels);
return await this.dbCache.getMessagesBefore(this.id, channelId, messageId, number, this.members, this.channels);
},
cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => {
return await this.updateMessageCache(channelId, messageId, null, cacheData, serverData);
@ -838,7 +840,7 @@ export default class ClientController extends EventEmitter {
let dataMessages = await this._queryServer('fetch-messages-after', channelId, messageId, number) as any[];
return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels));
},
cacheFunc: async () => { return await DBCache.getMessagesAfter(this.id, channelId, messageId, number, this.members, this.channels); },
cacheFunc: async () => { return await this.dbCache.getMessagesAfter(this.id, channelId, messageId, number, this.members, this.channels); },
cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => {
return await this.updateMessageCache(channelId, null, messageId, cacheData, serverData);
},
@ -858,7 +860,7 @@ export default class ClientController extends EventEmitter {
return resourceCacheDataBuff;
}
let cacheData = await DBCache.getResource(this.id, resourceId);
let cacheData = await this.dbCache.getResource(this.id, resourceId);
if (cacheData !== null) {
ResourceRAMCache.putResource(this.id, resourceId, cacheData.data);
return cacheData.data;
@ -868,7 +870,7 @@ export default class ClientController extends EventEmitter {
let serverData = await this._queryServer('fetch-resource', resourceId);
ResourceRAMCache.putResource(this.id, resourceId, serverData.data);
await DBCache.upsertServerResources(this.id, [ serverData ]);
await this.dbCache.upsertServerResources(this.id, [ serverData ]);
return serverData.data;
}

View File

@ -20,16 +20,18 @@ import { IAddServerData } from './elements/overlay-add-server';
export default class Controller extends EventEmitter {
public servers: ClientController[] = [];
constructor() {
constructor(
private dbCache: DBCache
) {
super();
}
async _connectFromConfig(serverConfig: ServerConfig): Promise<ClientController> {
LOG.debug(`connecting to server#${serverConfig.serverId} at ${serverConfig.url}`);
let server = new ClientController(serverConfig);
let server = new ClientController(this.dbCache, serverConfig);
await DBCache.clearAllMemberStatus(server.id);
await this.dbCache.clearAllMemberStatus(server.id);
this.servers.push(server);
@ -61,7 +63,7 @@ export default class Controller extends EventEmitter {
async init(): Promise<void> {
this.servers = [];
let serverConfigs = await DBCache.getServerConfigs();
let serverConfigs = await this.dbCache.getServerConfigs();
// TODO: HTML prompt if no server configs
if (serverConfigs.length == 0) {
@ -132,13 +134,13 @@ export default class Controller extends EventEmitter {
} else {
try {
let serverConfig: ServerConfig | null = null;
await DBCache.queueTransaction(async () => {
await this.dbCache.queueTransaction(async () => {
let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
let privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
let identityId = await DBCache.addIdentity(publicKeyPem, privateKeyPem);
let serverId = await DBCache.addServer(url, cert, name);
await DBCache.addServerIdentity(serverId, identityId);
serverConfig = await DBCache.getServerConfig(serverId, identityId);
let identityId = await this.dbCache.addIdentity(publicKeyPem, privateKeyPem);
let serverId = await this.dbCache.addServer(url, cert, name);
await this.dbCache.addServerIdentity(serverId, identityId);
serverConfig = await this.dbCache.getServerConfig(serverId, identityId);
});
if (serverConfig == null) {
throw new Error('unable to get server config');
@ -158,7 +160,7 @@ export default class Controller extends EventEmitter {
}
async removeServer(server: ClientController): Promise<void> {
await DBCache.removeServer(server.id);
await this.dbCache.removeServer(server.id);
this.servers = this.servers.filter(s => s != server);
}
}

View File

@ -12,30 +12,29 @@ import * as sqlite from 'sqlite';
import * as sqlite3 from 'sqlite3';
import { Message, Member, Channel, Resource, NotInitializedError, ServerMetaData, ServerConfig, CacheServerData } from './data-types';
let db: sqlite.Database | null = null;
// A cache implemented using an sqlite database
// Also stores configuration for server connections
export default class DBCache {
static TRANSACTION_QUEUE = new ConcurrentQueue(1);
private TRANSACTION_QUEUE = new ConcurrentQueue(1);
static async beginTransaction(): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('BEGIN TRANSACTION');
private constructor(
private readonly db: sqlite.Database
) {}
async beginTransaction(): Promise<void> {
await this.db.run('BEGIN TRANSACTION');
}
static async rollbackTransaction(): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('ROLLBACK');
async rollbackTransaction(): Promise<void> {
await this.db.run('ROLLBACK');
}
static async commitTransaction(): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('COMMIT');
async commitTransaction(): Promise<void> {
await this.db.run('COMMIT');
}
static async queueTransaction(func: (() => Promise<void>)): Promise<void> {
await DBCache.TRANSACTION_QUEUE.push(async () => {
async queueTransaction(func: (() => Promise<void>)): Promise<void> {
await this.TRANSACTION_QUEUE.push(async () => {
try {
await this.beginTransaction();
await func();
@ -47,22 +46,21 @@ export default class DBCache {
});
}
static async connect(): Promise<void> {
static async connect(): Promise<DBCache> {
try {
await fs.access('./db');
} catch (e) {
await fs.mkdir('./db');
}
db = await sqlite.open({
return new DBCache(await sqlite.open({
driver: sqlite3.Database,
filename: './db/cache.db'
});
}));
}
static async init(): Promise<void> {
async init(): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS identities (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
, public_key TEXT NOT NULL
@ -70,7 +68,7 @@ export default class DBCache {
)
`);
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS servers (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
, url TEXT NOT NULL
@ -81,7 +79,7 @@ export default class DBCache {
)
`);
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS server_identities (
server_id INTEGER NOT NULL
, identity_id INTEGER NOT NULL
@ -90,7 +88,7 @@ export default class DBCache {
)
`);
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS members (
id TEXT NOT NULL
, server_id INTEGER NOT NULL REFERENCES servers(id)
@ -105,7 +103,7 @@ export default class DBCache {
)
`);
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS channels (
id TEXT NOT NULL
, server_id INTEGER NOT NULL REFERENCES servers(id)
@ -116,7 +114,7 @@ export default class DBCache {
)
`);
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS resources (
id TEXT NOT NULL
, server_id INTEGER NOT NULL REFERENCES servers(id)
@ -127,10 +125,10 @@ export default class DBCache {
, CONSTRAINT resources_id_server_id_con UNIQUE (id, server_id)
)
`);
await db.run('CREATE INDEX IF NOT EXISTS resources_data_size_idx ON resources (data_size)');
await this.db.run('CREATE INDEX IF NOT EXISTS resources_data_size_idx ON resources (data_size)');
// note: no foreign key on resource_id since we may not have cached the resource yet
await db.run(`
await this.db.run(`
CREATE TABLE IF NOT EXISTS messages (
id TEXT NOT NULL
, server_id INTEGER NOT NULL REFERENCES servers(id)
@ -146,34 +144,31 @@ export default class DBCache {
, CONSTRAINT messages_id_server_id_con UNIQUE (id, server_id)
)
`);
await db.run('CREATE INDEX IF NOT EXISTS messages_id_idx ON messages (id)');
await db.run('CREATE INDEX IF NOT EXISTS messages_sent_dtg_idx ON messages (sent_dtg)');
await this.db.run('CREATE INDEX IF NOT EXISTS messages_id_idx ON messages (id)');
await this.db.run('CREATE INDEX IF NOT EXISTS messages_sent_dtg_idx ON messages (sent_dtg)');
});
}
static async close(): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.close();
async close(): Promise<void> {
await this.db.close();
}
// dangerous!
static async reset(): Promise<void> {
async reset(): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('DROP TABLE IF EXISTS identities');
await db.run('DROP TABLE IF EXISTS servers');
await db.run('DROP TABLE IF EXISTS server_identities');
await db.run('DROP TABLE IF EXISTS members');
await db.run('DROP TABLE IF EXISTS channels');
await db.run('DROP TABLE IF EXISTS resources');
await db.run('DROP TABLE IF EXISTS messages');
await this.db.run('DROP TABLE IF EXISTS identities');
await this.db.run('DROP TABLE IF EXISTS servers');
await this.db.run('DROP TABLE IF EXISTS server_identities');
await this.db.run('DROP TABLE IF EXISTS members');
await this.db.run('DROP TABLE IF EXISTS channels');
await this.db.run('DROP TABLE IF EXISTS resources');
await this.db.run('DROP TABLE IF EXISTS messages');
});
}
// returns the id of the identity inserted
static async addIdentity(publicKeyPem: string, privateKeyPem: string): Promise<number> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run(`
async addIdentity(publicKeyPem: string, privateKeyPem: string): Promise<number> {
let result = await this.db.run(`
INSERT INTO identities (public_key, private_key) VALUES (?, ?)
`, [ publicKeyPem, privateKeyPem ]);
if (!result || result.changes !== 1) {
@ -183,9 +178,8 @@ export default class DBCache {
}
// returns the id (client-side) of the server inserted
static async addServer(url: string, cert?: string, name?: string): Promise<number> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run(`
async addServer(url: string, cert?: string, name?: string): Promise<number> {
let result = await this.db.run(`
INSERT INTO servers (url, cert, name, icon_resource_id) VALUES (?, ?, ?, NULL)
`, [ url, cert, name ]);
if (!result || result.changes !== 1) {
@ -194,17 +188,15 @@ export default class DBCache {
return result.lastID as number;
}
static async removeServer(serverId: string): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run('DELETE FROM servers WHERE id=?', [ serverId ]);
async removeServer(serverId: string): Promise<void> {
let result = await this.db.run('DELETE FROM servers WHERE id=?', [ serverId ]);
if (result.changes != 1) {
throw new Error('unable to remove server');
}
}
static async addServerIdentity(serverId: number, identityId: number): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run(`
async addServerIdentity(serverId: number, identityId: number): Promise<void> {
let result = await this.db.run(`
INSERT INTO server_identities (server_id, identity_id) VALUES (?, ?)
`, [ serverId, identityId ]);
if (result.changes != 1) {
@ -212,27 +204,24 @@ export default class DBCache {
}
}
static async updateServer(serverId: string, serverMeta: ServerMetaData): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run('UPDATE servers SET name=?, icon_resource_id=? WHERE id=?', [ serverMeta.name, serverMeta.iconResourceId, serverId ]);
async updateServer(serverId: string, serverMeta: ServerMetaData): Promise<void> {
let result = await this.db.run('UPDATE servers SET name=?, icon_resource_id=? WHERE id=?', [ serverMeta.name, serverMeta.iconResourceId, serverId ]);
if (result.changes != 1) {
throw new Error('unable to update server');
}
}
static async updateServerMemberId(serverId: string, memberId: string): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.run('UPDATE servers SET member_id=? WHERE id=?', [ memberId, serverId ]);
async updateServerMemberId(serverId: string, memberId: string): Promise<void> {
let result = await this.db.run('UPDATE servers SET member_id=? WHERE id=?', [ memberId, serverId ]);
if (result.changes != 1) {
throw new Error(`unable to update member id, s#${serverId}, mem#${memberId}`);
}
}
static async updateServerMembers(serverId: string, members: Member[]): Promise<void> {
async updateServerMembers(serverId: string, members: Member[]): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('DELETE FROM members WHERE server_id=?', [ serverId ]);
let stmt = await db.prepare('INSERT INTO members (id, server_id, display_name, status, avatar_resource_id, role_name, role_color, role_priority, privileges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
await this.db.run('DELETE FROM members WHERE server_id=?', [ serverId ]);
let stmt = await this.db.prepare('INSERT INTO members (id, server_id, display_name, status, avatar_resource_id, role_name, role_color, role_priority, privileges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (let member of members) {
let result = await stmt.run([ member.id, serverId, member.displayName, member.status, member.avatarResourceId, member.roleName, member.roleColor, member.rolePriority, member.privileges?.join(',') ]);
if (result.changes != 1) {
@ -244,17 +233,15 @@ export default class DBCache {
});
}
static async clearAllMemberStatus(serverId: string): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run(`UPDATE members SET status='unknown' WHERE server_id=?`, [ serverId ]);
async clearAllMemberStatus(serverId: string): Promise<void> {
await this.db.run(`UPDATE members SET status='unknown' WHERE server_id=?`, [ serverId ]);
}
static async updateServerChannels(serverId: string, channels: Channel[]): Promise<void> {
async updateServerChannels(serverId: string, channels: Channel[]): Promise<void> {
console.log('setting to ' + channels.length + ' channels');
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('DELETE FROM channels WHERE server_id=?', [ serverId ]);
let stmt = await db.prepare('INSERT INTO channels (id, server_id, "index", name, flavor_text) VALUES (?, ?, ?, ?, ?)');
await this.db.run('DELETE FROM channels WHERE server_id=?', [ serverId ]);
let stmt = await this.db.prepare('INSERT INTO channels (id, server_id, "index", name, flavor_text) VALUES (?, ?, ?, ?, ?)');
for (let channel of channels) {
let result = await stmt.run([ channel.id, serverId, channel.index, channel.name, channel.flavorText ]);
if (result.changes != 1) {
@ -267,12 +254,11 @@ export default class DBCache {
}
// TODO: make this singular and a non-transaction based function?
static async upsertServerResources(serverId: string, resources: Resource[]): Promise<void> {
async upsertServerResources(serverId: string, resources: Resource[]): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
let currentSizeResult = await db.get('SELECT SUM(data_size) AS current_size FROM resources WHERE server_id=?', [ serverId ]);
let currentSizeResult = await this.db.get('SELECT SUM(data_size) AS current_size FROM resources WHERE server_id=?', [ serverId ]);
let currentSize = parseInt(currentSizeResult.current_size || 0);
let stmt = await db.prepare(`
let stmt = await this.db.prepare(`
INSERT INTO resources (id, server_id, hash, data, data_size, last_used) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT (id, server_id) DO UPDATE SET hash=?3, data=?4, last_used=?6
`);
@ -281,8 +267,8 @@ export default class DBCache {
continue;
}
while (resource.data.length + currentSize > Globals.MAX_SERVER_RESOURCE_CACHE_SIZE) {
let targetResult = await db.get('SELECT id, data_size FROM resources ORDER BY last_used ASC LIMIT 1');
let deleteResult = await db.run('DELETE FROM resources WHERE id=?', [ targetResult.id ]);
let targetResult = await this.db.get('SELECT id, data_size FROM resources ORDER BY last_used ASC LIMIT 1');
let deleteResult = await this.db.run('DELETE FROM resources WHERE id=?', [ targetResult.id ]);
if (deleteResult.changes != 1) {
throw new Error('failed to delete excess resource');
}
@ -298,10 +284,9 @@ export default class DBCache {
}
// Note: messages are directly from the server response, not parsed
static async upsertServerMessages(serverId: string, channelId: string, messages: Message[]): Promise<void> {
async upsertServerMessages(serverId: string, channelId: string, messages: Message[]): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
let stmt = await db.prepare(`
let stmt = await this.db.prepare(`
INSERT INTO messages (
id, server_id, channel_id, member_id, sent_dtg, text
, resource_id, resource_name, resource_width, resource_height, resource_preview_id
@ -326,7 +311,7 @@ export default class DBCache {
}
await stmt.finalize();
// delete the oldest messages if the cache is too big
await db.run(`
await this.db.run(`
DELETE FROM messages WHERE id IN (
SELECT id FROM messages WHERE server_id=?1 AND channel_id=?2 ORDER BY sent_dtg
LIMIT max(0, (SELECT COUNT(*) FROM messages WHERE server_id=?1 AND channel_id=?2) - ?3)
@ -336,15 +321,13 @@ export default class DBCache {
}
static async clearServerMessages(serverId: string, channelId: string): Promise<void> {
if (db === null) throw new NotInitializedError('db not initialized');
await db.run('DELETE FROM messages WHERE server_id=? AND channel_id=?', [ serverId, channelId ]);
async clearServerMessages(serverId: string, channelId: string): Promise<void> {
await this.db.run('DELETE FROM messages WHERE server_id=? AND channel_id=?', [ serverId, channelId ]);
}
static async deleteServerMessages(serverId: string, messageIds: string[]): Promise<void> {
async deleteServerMessages(serverId: string, messageIds: string[]): Promise<void> {
await this.queueTransaction(async () => {
if (db === null) throw new NotInitializedError('db not initialized');
let stmt = await db.prepare('DELETE FROM messages WHERE id=? AND server_id=?'); // include server_id for security purposes
let stmt = await this.db.prepare('DELETE FROM messages WHERE id=? AND server_id=?'); // include server_id for security purposes
for (let messageId of messageIds) {
let result = await stmt.run([ messageId, serverId ]);
if (result.changes != 1) {
@ -355,9 +338,8 @@ export default class DBCache {
});
}
static async getServerConfigs(): Promise<ServerConfig[]> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.all(`
async getServerConfigs(): Promise<ServerConfig[]> {
let result = await this.db.all(`
SELECT
servers.id AS server_id
, servers.url AS url
@ -376,9 +358,8 @@ export default class DBCache {
return result.map((dataServerConfig: any) => ServerConfig.fromDBData(dataServerConfig));
}
static async getServerConfig(serverId: number, identityId: number): Promise<ServerConfig> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.get(`
async getServerConfig(serverId: number, identityId: number): Promise<ServerConfig> {
let result = await this.db.get(`
SELECT
servers.id AS server_id
, servers.url AS url
@ -410,9 +391,8 @@ CREATE TABLE IF NOT EXISTS servers (
)
*/
static async getServer(serverId: string): Promise<CacheServerData | null> {
if (db === null) throw new NotInitializedError('db not initialized');
let result = await db.get(`
async getServer(serverId: string): Promise<CacheServerData | null> {
let result = await this.db.get(`
SELECT
id, url, cert, name,
icon_resource_id, member_id
@ -426,16 +406,14 @@ CREATE TABLE IF NOT EXISTS servers (
return CacheServerData.fromDBData(result);
}
static async getServerMemberId(serverId: string): Promise<string> {
if (db === null) throw new NotInitializedError('db not initialized');
let server = await db.get('SELECT member_id FROM servers WHERE id=?', [ serverId ]);
async getServerMemberId(serverId: string): Promise<string> {
let server = await this.db.get('SELECT member_id FROM servers WHERE id=?', [ serverId ]);
return server.member_id;
}
// returns null if no members
static async getMembers(serverId: string): Promise<Member[] | null> {
if (db === null) throw new NotInitializedError('db not initialized');
let members = await db.all('SELECT * FROM members WHERE server_id=?', [ serverId ]);
async getMembers(serverId: string): Promise<Member[] | null> {
let members = await this.db.all('SELECT * FROM members WHERE server_id=?', [ serverId ]);
if (members.length === 0) {
return null;
}
@ -443,9 +421,8 @@ CREATE TABLE IF NOT EXISTS servers (
}
// returns null if no channels
static async getChannels(serverId: string): Promise<Channel[] | null> {
if (db === null) throw new NotInitializedError('db not initialized');
let channels = await db.all('SELECT * FROM channels WHERE server_id=?', [ serverId ]);
async getChannels(serverId: string): Promise<Channel[] | null> {
let channels = await this.db.all('SELECT * FROM channels WHERE server_id=?', [ serverId ]);
if (channels.length === 0) {
return null;
}
@ -453,12 +430,11 @@ CREATE TABLE IF NOT EXISTS servers (
}
// returns [] if no messages found
static async getMessagesRecent(
async getMessagesRecent(
serverId: string, channelId: string, number: number,
members: Map<string, Member>, channels: Map<string, Channel>
): Promise<Message[]> {
if (db === null) throw new NotInitializedError('db not initialized');
let messages = await db.all(`
let messages = await this.db.all(`
SELECT * FROM (
SELECT
"id", "channel_id", "member_id"
@ -478,13 +454,12 @@ CREATE TABLE IF NOT EXISTS servers (
}
// returns null if no messages found
static async getMessagesBefore(
async getMessagesBefore(
serverId: string, channelId: string, messageId: string, number: number,
members: Map<string, Member>, channels: Map<string, Channel>
): Promise<Message[] | null> {
if (db === null) throw new NotInitializedError('db not initialized');
// Note: this query succeeds returning no results if the message with specified id is not found
let messages = await db.all(`
let messages = await this.db.all(`
SELECT * FROM (
SELECT
"id", "channel_id", "member_id"
@ -510,13 +485,12 @@ CREATE TABLE IF NOT EXISTS servers (
}
// returns null if no messages found
static async getMessagesAfter(
async getMessagesAfter(
serverId: string, channelId: string, messageId: string, number: number,
members: Map<string, Member>, channels: Map<string, Channel>
): Promise<Message[] | null> {
if (db === null) throw new NotInitializedError('db not initialized');
// Note: this query succeeds returning no results if the message with specified id is not found
let messages = await db.all(`
let messages = await this.db.all(`
SELECT
"id", "channel_id", "member_id"
, "sent_dtg", "text"
@ -539,10 +513,9 @@ CREATE TABLE IF NOT EXISTS servers (
return messages.map((messageData: any) => Message.fromDBData(messageData, members, channels));
}
static async getResource(serverId: string, resourceId: string): Promise<Resource | null> {
if (db === null) throw new NotInitializedError('db not initialized');
let row = await db.get('SELECT id, data, hash FROM resources WHERE server_id=? AND id=?', [ serverId, resourceId ]);
await db.run('UPDATE resources SET last_used=?1 WHERE server_id=?2 AND id=?3', [ new Date().getTime(), serverId, resourceId ]);
async getResource(serverId: string, resourceId: string): Promise<Resource | null> {
let row = await this.db.get('SELECT id, data, hash FROM resources WHERE server_id=? AND id=?', [ serverId, resourceId ]);
await this.db.run('UPDATE resources SET last_used=?1 WHERE server_id=?2 AND id=?3', [ new Date().getTime(), serverId, resourceId ]);
if (!row) {
return null;
}

View File

@ -41,12 +41,12 @@ window.addEventListener('DOMContentLoaded', () => {
await LOG.ensureSourceMaps();
LOG.silly('web client log source maps loaded');
await DBCache.connect();
await DBCache.init();
const dbCache = await DBCache.connect();
await dbCache.init();
LOG.silly('cache initialized');
const controller = new Controller();
const controller = new Controller(dbCache);
await controller.init();
LOG.silly('controller initialized');