diff --git a/src/class/Client.ts b/src/class/Client.ts index 5a733f1..1c83c3b 100644 --- a/src/class/Client.ts +++ b/src/class/Client.ts @@ -17,14 +17,14 @@ export default class Client extends eris.Client { public serverManagement: ServerManagement; - public db: { Member: mongoose.Model, Moderation: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, local: LocalStorage }; + public db: { Member: mongoose.Model, Moderation: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, local: { muted: LocalStorage } }; constructor(token: string, options?: eris.ClientOptions) { super(token, options); this.commands = new Collection(); this.events = new Collection(); this.intervals = new Collection(); - this.db = { Member, Moderation, PagerNumber, Rank, Redirect, local: new LocalStorage(this) }; + this.db = { Member, Moderation, PagerNumber, Rank, Redirect, local: { muted: new LocalStorage('muted') } }; } public async loadDatabase() { diff --git a/src/class/LocalStorage.ts b/src/class/LocalStorage.ts index 99765e6..4827d07 100644 --- a/src/class/LocalStorage.ts +++ b/src/class/LocalStorage.ts @@ -1,37 +1,65 @@ /* eslint-disable no-constant-condition */ -import { promises as fs, constants } from 'fs'; -import { Client } from '.'; +import { promises as fs, accessSync, constants, writeFileSync } from 'fs'; +import { promisify } from 'util'; +import { gzip, gzipSync, unzip } from 'zlib'; type JSONData = [{key: string, value: any}?]; /** * Persistant local JSON-based storage. - * Auto-locking system to prevent corrupted data. + * - auto-locking system to prevent corrupted data + * - uses gzip compression to keep DB storage space utilization low * @author Matthew */ export default class LocalStorage { - private client: Client; - protected storagePath: string; private locked: boolean = false; - constructor(client: Client, storagePath = `${__dirname}/../../localstorage`) { - this.client = client; - this.storagePath = storagePath; + constructor(dbName: string) { + this.storagePath = `${__dirname}/../../localstorage/${dbName}.json.gz`; this.init(); } - private async init() { + private init() { try { - await fs.access(`${this.storagePath}/1.json`, constants.F_OK); + accessSync(this.storagePath, constants.F_OK); } catch { const setup = []; - await fs.writeFile(`${this.storagePath}/1.json`, JSON.stringify(setup), { encoding: 'utf8' }); + const data = gzipSync(JSON.stringify(setup)); + writeFileSync(this.storagePath, data); } } + /** + * Compresses data using gzip. + * @param data The data to be compressed. + * ```ts + * await LocalStorage.compress('hello!'); + * ``` + */ + static async compress(data: string): Promise { + const func = promisify(gzip); + const comp = await func(data); + return comp; + } + + /** + * Decompresses data using gzip. + * @param data The data to be decompressed. + * ```ts + * const compressed = await LocalStorage.compress('data'); + * const decompressed = await LocalStorage.decompress(compressed); + * console.log(decompressed); // logs 'data'; + * ``` + */ + static async decompress(data: Buffer): Promise { + const func = promisify(unzip); + const uncomp = await func(data); + return uncomp.toString(); + } + /** * Retrieves one data from the store. * If the store has multiple entries for the same key, this function will only return the first entry. @@ -46,9 +74,10 @@ export default class LocalStorage { } this.locked = true; - const file = await fs.readFile(`${this.storagePath}/1.json`, { encoding: 'utf8' }); + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); this.locked = false; - const json: JSONData = JSON.parse(file); + const json: JSONData = JSON.parse(uncomp); const result = json.filter((data) => data.key === key); if (!result[0]) return null; return result[0].value; @@ -67,9 +96,10 @@ export default class LocalStorage { } this.locked = true; - const file = await fs.readFile(`${this.storagePath}/1.json`, { encoding: 'utf8' }); + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); this.locked = false; - const json: JSONData = JSON.parse(file); + const json: JSONData = JSON.parse(uncomp); const result = json.filter((data) => data.key === key); if (result.length < 1) return null; return result; @@ -90,10 +120,12 @@ export default class LocalStorage { } this.locked = true; - const file = await fs.readFile(`${this.storagePath}/1.json`, { encoding: 'utf8' }); - const json: JSONData = JSON.parse(file); + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + const json: JSONData = JSON.parse(uncomp); json.push({ key, value }); - await fs.writeFile(`${this.storagePath}/1.json`, JSON.stringify(json), { encoding: 'utf8' }); + const comp = await LocalStorage.compress(JSON.stringify(json)); + await fs.writeFile(this.storagePath, comp); this.locked = false; } @@ -111,10 +143,12 @@ export default class LocalStorage { } this.locked = true; - const file = await fs.readFile(`${this.storagePath}/1.json`, { encoding: 'utf8' }); - const json: JSONData = JSON.parse(file); + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + const json: JSONData = JSON.parse(uncomp); const filtered = json.filter((data) => data.key !== key); - await fs.writeFile(`${this.storagePath}/1.json`, JSON.stringify(filtered), { encoding: 'utf8' }); + const comp = await LocalStorage.compress(JSON.stringify(filtered)); + await fs.writeFile(this.storagePath, comp); this.locked = false; } } diff --git a/src/class/Moderation.ts b/src/class/Moderation.ts index 45f1c4e..4b457e6 100644 --- a/src/class/Moderation.ts +++ b/src/class/Moderation.ts @@ -134,7 +134,7 @@ export default class Moderation { } else date = null; const expiration = { date, processed }; mod.expiration = expiration; - await this.client.db.local.set(`muted-${member.id}`, true); + await this.client.db.local.muted.set(`muted-${member.id}`, true); const embed = new RichEmbed(); embed.setTitle(`Case ${logID} | Mute`); @@ -171,7 +171,7 @@ export default class Moderation { date: new Date(), }); - await this.client.db.local.del(`muted-${member.id}`); + await this.client.db.local.muted.del(`muted-${member.id}`); const embed = new RichEmbed(); embed.setTitle(`Case ${logID} | Unmute`); diff --git a/src/commands/mute.ts b/src/commands/mute.ts index 73beb94..65d47a8 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -20,7 +20,7 @@ export default class Mute extends Command { if (!member) return this.error(message.channel, 'Cannot find user.'); try { - const res1 = await this.client.db.local.get(`muted-${member.id}`); + const res1 = await this.client.db.local.muted.get(`muted-${member.id}`); if (res1 || this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already muted.'); } catch {} // eslint-disable-line no-empty if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.'); diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts index d642481..170b245 100644 --- a/src/commands/unmute.ts +++ b/src/commands/unmute.ts @@ -19,7 +19,7 @@ export default class Unmute extends Command { if (!member) return this.error(message.channel, 'Cannot find user.'); try { - const res1 = await this.client.db.local.get(`muted-${member.id}`); + const res1 = await this.client.db.local.muted.get(`muted-${member.id}`); if (!res1 || !this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already unmuted.'); } catch {} // eslint-disable-line no-empty if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.'); diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index afc41a0..bdd7ea5 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -11,7 +11,7 @@ export default class GuildMemberAdd extends Event { public async run(_, member: Member) { try { - const search = await this.client.db.local.get(`muted-${member.user.id}`); + const search = await this.client.db.local.muted.get(`muted-${member.user.id}`); if (search === true) { member.addRole('478373942638149643'); } diff --git a/src/intervals/autoRelease.ts b/src/intervals/autoRelease.ts index 20580b5..865c2dd 100644 --- a/src/intervals/autoRelease.ts +++ b/src/intervals/autoRelease.ts @@ -18,7 +18,7 @@ export default function checkLock(client: Client): NodeJS.Timeout { await client.util.moderation.unban(moderation.userID, system); break; case 2: - if (await client.db.local.get(`muted-${moderation.userID}`) === true) { + if (await client.db.local.muted.get(`muted-${moderation.userID}`) === true) { await client.util.moderation.unmute(moderation.userID, system); } break;