From 21b1152f5357f47586456949cadfb9876a0613da Mon Sep 17 00:00:00 2001 From: Reto Brunner Date: Sat, 4 Nov 2023 17:30:43 +0100 Subject: [PATCH] cleaner: expose cli task to do cleaning + vacuum Make the cleaner available to users by exposing it as a subcommand to thelounge storage. This is recommended to be run whenever the storage policy significantly changes in a way that makes many messages eligible for deletion. The cleaner would cope, but it'll be inefficient and can take many hours. Due to how storage works in sqlite, the space would not actually be given back to the OS, just marked for future writes. Hence this also runs a vacuum to compact the DB as much as it can. --- server/command-line/storage.ts | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/server/command-line/storage.ts b/server/command-line/storage.ts index df9acea0..3f9184b2 100644 --- a/server/command-line/storage.ts +++ b/server/command-line/storage.ts @@ -3,6 +3,7 @@ import {Command} from "commander"; import ClientManager from "../clientManager"; import Utils from "./utils"; import SqliteMessageStorage from "../plugins/messageStorage/sqlite"; +import {StorageCleaner} from "../storageCleaner"; const program = new Command("storage").description( "various utilities related to the message storage" @@ -10,7 +11,7 @@ const program = new Command("storage").description( program .command("migrate") - .argument("[user]", "migrate a specific user only, all if not provided") + .argument("[username]", "migrate a specific user only, all if not provided") .description("Migrate message storage where needed") .on("--help", Utils.extraHelp) .action(function (user) { @@ -20,7 +21,19 @@ program }); }); -async function runMigrations(user: string) { +program + .command("clean") + .argument("[user]", "clean messages for a specific user only, all if not provided") + .description("Delete messages from the DB based on the storage policy") + .on("--help", Utils.extraHelp) + .action(function (user) { + runCleaning(user).catch((err) => { + log.error(err.toString()); + process.exit(1); + }); + }); + +async function runMigrations(user?: string) { const manager = new ClientManager(); const users = manager.getUsers(); @@ -65,4 +78,46 @@ function isUserLogEnabled(manager: ClientManager, user: string): boolean { return conf.log; } +async function runCleaning(user: string) { + const manager = new ClientManager(); + const users = manager.getUsers(); + + if (user) { + if (!users.includes(user)) { + throw new Error(`invalid user ${user}`); + } + + return cleanUser(manager, user); + } + + for (const name of users) { + await cleanUser(manager, name); + // if any migration fails we blow up, + // chances are the rest won't complete either + } +} + +async function cleanUser(manager: ClientManager, user: string) { + log.info("handling user", user); + + if (!isUserLogEnabled(manager, user)) { + log.info("logging disabled for user", user, ". Skipping"); + return; + } + + const sqlite = new SqliteMessageStorage(user); + await sqlite.enable(); + const cleaner = new StorageCleaner(sqlite); + const num_deleted = await cleaner.runDeletesNoLimit(); + log.info(`deleted ${num_deleted} messages`); + log.info("running a vacuum now, this might take a while"); + + if (num_deleted > 0) { + await sqlite.vacuum(); + } + + await sqlite.close(); + log.info(`cleaning messages for ${user} has been successful`); +} + export default program;