dbcache no longer is a static singleton
This commit is contained in:
parent
9a17a1d97f
commit
cf39e3d29c
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
|
Loading…
Reference in New Issue
Block a user