Merge pull request #3158 from SillyTavern/max-total-chat-backups Add maxTotalChatBackups config.yaml value
Signed| @@ -98,6 +98,8 @@ skipContentCheck: false | |||
| 98 | disableChatBackup: false | 98 | disableChatBackup: false |
| 99 | # Number of backups to keep for each chat and settings file | 99 | # Number of backups to keep for each chat and settings file |
| 100 | numberOfBackups: 50 | 100 | numberOfBackups: 50 |
| 101 | # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups. | ||
| 102 | maxTotalChatBackups: -1 | ||
| 101 | # Interval in milliseconds to throttle chat backups per user | 103 | # Interval in milliseconds to throttle chat backups per user |
| 102 | chatBackupThrottleInterval: 10000 | 104 | chatBackupThrottleInterval: 10000 |
| 103 | # Allowed hosts for card downloads | 105 | # Allowed hosts for card downloads |
| @@ -11,6 +11,10 @@ import _ from 'lodash'; | |||
| 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; | 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 12 | import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } from '../util.js'; | 12 | import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } from '../util.js'; |
| 13 | 13 | ||
| 14 | const isBackupDisabled = getConfigValue('disableChatBackup', false); | ||
| 15 | const maxTotalChatBackups = Number(getConfigValue('maxTotalChatBackups', -1)); | ||
| 16 | const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000); | ||
| 17 | |||
| 14 | /** | 18 | /** |
| 15 | * Saves a chat to the backups directory. | 19 | * Saves a chat to the backups directory. |
| 16 | * @param {string} directory The user's backups directory. | 20 | * @param {string} directory The user's backups directory. |
| @@ -19,7 +23,6 @@ import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, | |||
| 19 | */ | 23 | */ |
| 20 | function backupChat(directory, name, chat) { | 24 | function backupChat(directory, name, chat) { |
| 21 | try { | 25 | try { |
| 22 | const isBackupDisabled = getConfigValue('disableChatBackup', false); | ||
| 23 | 26 | ||
| 24 | if (isBackupDisabled) { | 27 | if (isBackupDisabled) { |
| 25 | return; | 28 | return; |
| @@ -32,6 +35,12 @@ function backupChat(directory, name, chat) { | |||
| 32 | writeFileAtomicSync(backupFile, chat, 'utf-8'); | 35 | writeFileAtomicSync(backupFile, chat, 'utf-8'); |
| 33 | 36 | ||
| 34 | removeOldBackups(directory, `chat_${name}_`); | 37 | removeOldBackups(directory, `chat_${name}_`); |
| 38 | |||
| 39 | if (isNaN(maxTotalChatBackups) || maxTotalChatBackups < 0) { | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | removeOldBackups(directory, 'chat_', maxTotalChatBackups); | ||
| 35 | } catch (err) { | 44 | } catch (err) { |
| 36 | console.log(`Could not backup chat for ${name}`, err); | 45 | console.log(`Could not backup chat for ${name}`, err); |
| 37 | } | 46 | } |
| @@ -45,7 +54,6 @@ const backupFunctions = new Map(); | |||
| 45 | * @returns {function(string, string, string): void} Backup function | 54 | * @returns {function(string, string, string): void} Backup function |
| 46 | */ | 55 | */ |
| 47 | function getBackupFunction(handle) { | 56 | function getBackupFunction(handle) { |
| 48 | const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000); | ||
| 49 | if (!backupFunctions.has(handle)) { | 57 | if (!backupFunctions.has(handle)) { |
| 50 | backupFunctions.set(handle, _.throttle(backupChat, throttleInterval, { leading: true, trailing: true })); | 58 | backupFunctions.set(handle, _.throttle(backupChat, throttleInterval, { leading: true, trailing: true })); |
| 51 | } | 59 | } |
| @@ -376,16 +376,24 @@ export function generateTimestamp() { | |||
| 376 | * Remove old backups with the given prefix from a specified directory. | 376 | * Remove old backups with the given prefix from a specified directory. |
| 377 | * @param {string} directory The root directory to remove backups from. | 377 | * @param {string} directory The root directory to remove backups from. |
| 378 | * @param {string} prefix File prefix to filter backups by. | 378 | * @param {string} prefix File prefix to filter backups by. |
| 379 | * @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `numberOfBackups` config value. | ||
| 379 | */ | 380 | */ |
| 380 | export function removeOldBackups(directory, prefix) { | 381 | export function removeOldBackups(directory, prefix, limit = null) { |
| 381 | const MAX_BACKUPS = Number(getConfigValue('numberOfBackups', 50)); | 382 | const MAX_BACKUPS = limit ?? Number(getConfigValue('numberOfBackups', 50)); |
| 382 | 383 | ||
| 383 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); | 384 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); |
| 384 | if (files.length > MAX_BACKUPS) { | 385 | if (files.length > MAX_BACKUPS) { |
| 385 | files = files.map(f => path.join(directory, f)); | 386 | files = files.map(f => path.join(directory, f)); |
| 386 | files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs); | 387 | files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs); |
| 387 | 388 | ||
| 388 | fs.rmSync(files[0]); | 389 | while (files.length > MAX_BACKUPS) { |
| 390 | const oldest = files.shift(); | ||
| 391 | if (!oldest) { | ||
| 392 | break; | ||
| 393 | } | ||
| 394 | |||
| 395 | fs.rmSync(oldest); | ||
| 396 | } | ||
| 389 | } | 397 | } |
| 390 | } | 398 | } |
| 391 | 399 | ||