Add setting to specify minimum age of open group messages to prune.

When an open group has more than 2000 messages, those older than the
specified number of months will be pruned on application start-up.

Fixes #2310.
This commit is contained in:
Ian Macdonald 2022-05-14 18:23:51 +02:00 committed by Audric Ackermann
parent ea24da0f28
commit 695e867221
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
6 changed files with 96 additions and 33 deletions

View File

@ -137,6 +137,10 @@
"typingIndicatorsSettingDescription": "See and share when messages are being typed (applies to all sessions).",
"typingIndicatorsSettingTitle": "Typing Indicators",
"zoomFactorSettingTitle": "Zoom Factor",
"pruneSettingTitle": "Prune Old Open Group Messages",
"pruneSettingDescription": "When Session starts, prune messages older than this from groups with > 2000 messages (0 = no pruning)",
"pruneSettingUnit": "month",
"pruneSettingUnits": "months",
"notificationSettingsDialog": "When messages arrive, display notifications that reveal...",
"disableNotifications": "Mute notifications",
"nameAndMessage": "Name and content",

View File

@ -0,0 +1,36 @@
import Slider from 'rc-slider';
import React from 'react';
// tslint:disable-next-line: no-submodule-imports
import useUpdate from 'react-use/lib/useUpdate';
import { SessionSettingsItemWrapper } from './SessionSettingListItem';
import { ToastUtils } from '../../session/utils';
export const PruningSessionSlider = (props: { onSliderChange?: (value: number) => void }) => {
const forceUpdate = useUpdate();
const handleSlider = (valueToForward: number) => {
props?.onSliderChange?.(valueToForward);
window.setSettingValue('prune-setting', valueToForward);
ToastUtils.pushRestartNeeded();
forceUpdate();
};
const currentValueFromSettings = window.getSettingValue('prune-setting') || 0;
return (
<SessionSettingsItemWrapper title={window.i18n('pruneSettingTitle')} description={window.i18n('pruneSettingDescription')} inline={false}>
<div className="slider-wrapper">
<Slider
dots={true}
step={1}
min={0}
max={12}
defaultValue={currentValueFromSettings}
onAfterChange={handleSlider}
/>
<div className="slider-info">
<p>{currentValueFromSettings} {currentValueFromSettings === 1 ? window.i18n('pruneSettingUnit') : window.i18n('pruneSettingUnits')}</p>
</div>
</div>
</SessionSettingsItemWrapper>
);
};

View File

@ -13,6 +13,7 @@ import { SessionButtonColor } from '../../basic/SessionButton';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
import { ZoomingSessionSlider } from '../ZoomingSessionSlider';
import { PruningSessionSlider } from '../PruningSessionSlider';
async function toggleLinkPreviews() {
const newValue = !window.getSettingValue('link-preview-setting');
@ -119,6 +120,7 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
description={window.i18n('audioMessageAutoplayDescription')}
active={audioAutoPlay}
/>
<PruningSessionSlider />
<ZoomingSessionSlider />
<SessionSettingButtonItem
title={window.i18n('surveyTitle')}

View File

@ -98,6 +98,16 @@ async function getSpellCheckSetting() {
return json.value;
}
async function getPruneSetting() {
const json = sqlNode.getItemById('prune-setting');
// Default to `6` if setting doesn't exist yet
if (!json) {
return 6;
}
return json.value;
}
function showWindow() {
if (!mainWindow) {
return;
@ -750,9 +760,12 @@ async function showMainWindow(sqlKey: string, passwordAttempt = false) {
messages: locale.messages,
passwordAttempt,
});
const pruneSetting = await getPruneSetting();
appStartInitialSpellcheckSetting = await getSpellCheckSetting();
sqlChannels.initializeSqlChannel();
sqlNode.cleanUpOldOpengroups(pruneSetting);
await initAttachmentsChannel({
userDataPath,
});

View File

@ -1550,7 +1550,6 @@ async function initializeSql({
console.info('total message count before cleaning: ', getMessageCount());
console.info('total conversation count before cleaning: ', getConversationCount());
cleanUpOldOpengroups();
cleanUpUnusedNodeForKeyEntries();
printDbStats();
@ -3405,7 +3404,7 @@ function cleanUpMessagesJson() {
console.info(`cleanUpMessagesJson took ${Date.now() - start}ms`);
}
function cleanUpOldOpengroups() {
function cleanUpOldOpengroups(pruneSetting: number) {
const ourNumber = getItemById('number_id');
if (!ourNumber || !ourNumber.value) {
console.info('cleanUpOldOpengroups: ourNumber is not set');
@ -3429,42 +3428,46 @@ function cleanUpOldOpengroups() {
db.transaction(() => {
dropFtsAndTriggers(db);
v2Convos.forEach(convo => {
const convoId = convo.id;
const messagesInConvoBefore = getMessagesCountByConversation(convoId);
if (pruneSetting !== 0) {
v2Convos.forEach(convo => {
const convoId = convo.id;
const messagesInConvoBefore = getMessagesCountByConversation(convoId);
if (messagesInConvoBefore >= maxMessagePerOpengroupConvo) {
const minute = 1000 * 60;
const sixMonths = minute * 60 * 24 * 30 * 6;
const messagesTimestampToRemove = Date.now() - sixMonths;
const countToRemove = assertGlobalInstance()
.prepare(
`SELECT count(*) from ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId;`
)
.get({ conversationId: convoId, serverTimestamp: Date.now() - sixMonths })['count(*)'];
const start = Date.now();
if (messagesInConvoBefore >= maxMessagePerOpengroupConvo) {
const minute = 1000 * 60;
const userDefinedMonths = minute * 60 * 24 * 30 * pruneSetting;
const messagesTimestampToRemove = Date.now() - userDefinedMonths;
const countToRemove = assertGlobalInstance()
.prepare(
`SELECT count(*) from ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId;`
)
.get({ conversationId: convoId, serverTimestamp: Date.now() - userDefinedMonths })['count(*)'];
const start = Date.now();
assertGlobalInstance()
.prepare(
`
DELETE FROM ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId`
)
.run({ conversationId: convoId, serverTimestamp: messagesTimestampToRemove }); // delete messages older than sixMonths
const messagesInConvoAfter = getMessagesCountByConversation(convoId);
assertGlobalInstance()
.prepare(
`
DELETE FROM ${MESSAGES_TABLE} WHERE serverTimestamp <= $serverTimestamp AND conversationId = $conversationId`
)
.run({ conversationId: convoId, serverTimestamp: messagesTimestampToRemove }); // delete messages older than the user-specified age.
const messagesInConvoAfter = getMessagesCountByConversation(convoId);
console.info(
`Cleaning ${countToRemove} messages older than 6 months in public convo: ${convoId} took ${Date.now() -
start}ms. Old message count: ${messagesInConvoBefore}, new message count: ${messagesInConvoAfter}`
);
console.info(
`Cleaning ${countToRemove} messages older than ${pruneSetting} months in public convo: ${convoId} took ${Date.now() -
start}ms. Old message count: ${messagesInConvoBefore}, new message count: ${messagesInConvoAfter}`
);
const unreadCount = getUnreadCountByConversation(convoId);
const convoProps = getConversationById(convoId);
if (convoProps) {
convoProps.unreadCount = unreadCount;
updateConversation(convoProps);
const unreadCount = getUnreadCountByConversation(convoId);
const convoProps = getConversationById(convoId);
if (convoProps) {
convoProps.unreadCount = unreadCount;
updateConversation(convoProps);
}
}
}
});
});
} else {
console.info('Skipping cleaning messages in public convos.');
};
// now, we might have a bunch of private conversation, without any interaction and no messages
// those are the conversation of the old members in the opengroups we just cleaned.
@ -3680,6 +3683,7 @@ export const sqlNode = {
getPubkeysInPublicConversation,
getAllGroupsInvolvingId,
removeAllConversations,
cleanUpOldOpengroups,
searchConversations,
searchMessages,

View File

@ -162,6 +162,10 @@ export type LocalizerKeys =
| 'copySessionID'
| 'timerOption_0_seconds'
| 'zoomFactorSettingTitle'
| 'pruneSettingTitle'
| 'pruneSettingDescription'
| 'pruneSettingUnit'
| 'pruneSettingUnits'
| 'unableToCall'
| 'callMissedTitle'
| 'done'