diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 21ce72678..49480428e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -464,5 +464,6 @@ "startedACall": "You called $name$", "answeredACall": "Call with $name$", "trimDatabase": "Trim Database", - "trimDatabaseDescription": "Reduces your message database size to your last 10,000 messages." + "trimDatabaseDescription": "Reduces your message database size to your last 10,000 messages.", + "trimDatabaseConfirmationBody": "Are you sure you want to delete your $deleteAmount$ oldest received messages?" } diff --git a/app/sql.js b/app/sql.js index 14abeeba1..3fbeb135f 100644 --- a/app/sql.js +++ b/app/sql.js @@ -7,6 +7,7 @@ const { redactAll } = require('../js/modules/privacy'); const { remove: removeUserConfig } = require('./user_config'); const { map, isString, fromPairs, forEach, last, isEmpty, isObject, isNumber } = require('lodash'); +const { requestSnodesForPubkey } = require('../ts/session/apis/snode_api/SNodeAPI'); /* eslint-disable camelcase */ @@ -2283,29 +2284,144 @@ function getFirstUnreadMessageIdInConversation(conversationId) { /** * Deletes all but the 10,000 last received messages. */ -function trimMessages() { - globalInstance +function trimMessages(limit) { + // METHOD 1 Start - Seems to take to long and freeze + // const convoCount = globalInstance + // .prepare( + // ` + // SELECT COUNT(*) FROM ${MESSAGES_TABLE} + // WHERE conversationId = $conversationId + // ` + // ) + // .all({ + // conversationId, + // }); + + // if (convoCount < limit) { + // console.log(`Skipping conversation: ${conversationId}`); + // return; + // } else { + // console.count('convo surpassed limit'); + // } + + // globalInstance + // .prepare( + // ` + // DELETE FROM ${MESSAGES_TABLE} + // WHERE conversationId = $conversationId + // AND id NOT IN ( + // SELECT id FROM ${MESSAGES_TABLE} + // WHERE conversationId = $conversationId + // ORDER BY received_at DESC + // LIMIT $limit + // ); + // ` + // ) + // .run({ + // conversationId, + // limit, + // }); + + // METHOD 1 END + + // METHOD 2 Start + const messages = globalInstance .prepare( ` - DELETE FROM ${MESSAGES_TABLE} - WHERE id NOT IN ( - SELECT id FROM ${MESSAGES_TABLE} - ORDER BY received_at DESC - LIMIT 10000 - ); - ` - ) - .run(); + SELECT id, conversationId FROM ${MESSAGES_TABLE} - const rows = globalInstance - .prepare( - `SELECT * FROM ${MESSAGES_TABLE} - ORDER BY received_at DESC;` + CREATE VIRTUAL TABLE IF NOT EXISTS temp_deletion + id STRING PRIMARY KEY ASC + ` ) .all(); - return rows; - // return map(rows, row => jsonToObject(row.json)); + let idsToDelete = []; + const convoCountLookup = {}; + + for (let index = 0; index < messages.length; index++) { + const { conversationId, id } = messages[index]; + console.log(`run ${index} - convoId: ${conversationId}, messageId: ${id}`); + + if (!convoCountLookup[conversationId]) { + convoCountLookup[conversationId] = 1; + } else { + convoCountLookup[conversationId]++; + if (convoCountLookup[conversationId] > limit) { + idsToDelete.push(id); + } + } + } + + // Ideally should be able to do WHERE id IN (x, y, z) with an array of IDs + // the array might need to be chunked as well for performance + const idSlice = [...idsToDelete].slice(0, 30); + idSlice.forEach(id => { + globalInstance + .prepare( + ` + DELETE FROM ${MESSAGES_TABLE} + WHERE id = $idSlice + ` + ) + .run({ + idSlice, + }); + }); + + // Method 2 End + + // Method 3 start - Audric's suggestion + + // const largeConvos = globalInstance + // .prepare( + // ` + // SELECT conversationId, count(id) FROM ${MESSAGES_TABLE} + // GROUP BY conversationId + // HAVING COUNT(id) > 1000 + // ` + // ) + // .all(); + + // console.log({ largeConvos }); + + // // finding 1000th msg timestamp + // largeConvos.forEach(convo => { + // const convoId = convo.conversationId; + // console.log({ convoId }); + // const lastMsg = globalInstance + // .prepare( + // ` + // SELECT received_at, sent_at FROM ${MESSAGES_TABLE} + // WHERE conversationId = $convoId + // ORDER BY received_at DESC + // LIMIT 1 + // OFFSET 999 + // ` + // ) + // .all({ + // convoId, + // }); + + // // use timestamp with lesserThan as conditional for deletion + // console.log({ lastMsg }); + // const timestamp = lastMsg[0].received_at; + // if (timestamp) { + // console.log({ timestamp, convoId }); + // globalInstance + // .prepare( + // ` + // DELETE FROM ${MESSAGES_TABLE} + // WHERE conversationId = $convoId + // AND received_at < $timestamp + // ` + // ) + // .run({ + // timestamp, + // convoId, + // }); + // } + // }); } function getMessagesBySentAt(sentAt) { diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index b30249321..100d27b9f 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -1455,7 +1455,7 @@ .module-search-results { overflow-y: auto; max-height: 100%; - color: white; + color: var(--color-text); } .module-search-results__conversations-header { @@ -1547,7 +1547,7 @@ white-space: nowrap; text-overflow: ellipsis; - color: var(--color-text-subtle); + color: var(--color-text); } .module-message-search-result__header__timestamp { @@ -1581,7 +1581,7 @@ font-size: 13px; - color: $color-gray-60; + color: var(--color-text-subtle); max-height: 3.6em; diff --git a/ts/components/basic/MessageBodyHighlight.tsx b/ts/components/basic/MessageBodyHighlight.tsx new file mode 100644 index 000000000..f0948c316 --- /dev/null +++ b/ts/components/basic/MessageBodyHighlight.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { RenderTextCallbackType } from '../../types/Util'; +import { SizeClassType } from '../../util/emoji'; +import { AddNewLines } from '../conversation/AddNewLines'; +import { Emojify } from '../conversation/Emojify'; +import { MessageBody } from '../conversation/message/message-content/MessageBody'; + +interface Props { + text: string; +} + +const renderNewLines: RenderTextCallbackType = ({ text, key }) => ( + +); + +const renderEmoji = ({ + text, + key, + sizeClass, + renderNonEmoji, +}: { + text: string; + key: number; + sizeClass?: SizeClassType; + renderNonEmoji: RenderTextCallbackType; +}) => ; + +export const MessageBodyHighlight = (props: Props) => { + const { text } = props; + const results: Array = []; + const FIND_BEGIN_END = /<>(.+?)<>/g; + + let match = FIND_BEGIN_END.exec(text); + let last = 0; + let count = 1; + + if (!match) { + return ; + } + + const sizeClass = ''; + + while (match) { + if (last < match.index) { + const beforeText = text.slice(last, match.index); + results.push( + renderEmoji({ + text: beforeText, + sizeClass, + key: count++, + renderNonEmoji: renderNewLines, + }) + ); + } + + const [, toHighlight] = match; + results.push( + + {renderEmoji({ + text: toHighlight, + sizeClass, + key: count++, + renderNonEmoji: renderNewLines, + })} + + ); + + // @ts-ignore + last = FIND_BEGIN_END.lastIndex; + match = FIND_BEGIN_END.exec(text); + } + + if (last < text.length) { + results.push( + renderEmoji({ + text: text.slice(last), + sizeClass, + key: count++, + renderNonEmoji: renderNewLines, + }) + ); + } + + return <>{results}; +}; diff --git a/ts/components/basic/MessageBodyHighlist.tsx b/ts/components/basic/MessageBodyHighlist.tsx deleted file mode 100644 index 520cff140..000000000 --- a/ts/components/basic/MessageBodyHighlist.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import { RenderTextCallbackType } from '../../types/Util'; -import { SizeClassType } from '../../util/emoji'; -import { AddNewLines } from '../conversation/AddNewLines'; -import { Emojify } from '../conversation/Emojify'; -import { MessageBody } from '../conversation/message/message-content/MessageBody'; - -interface Props { - text: string; -} - -const renderNewLines: RenderTextCallbackType = ({ text, key }) => ( - -); - -const renderEmoji = ({ - text, - key, - sizeClass, - renderNonEmoji, -}: { - text: string; - key: number; - sizeClass?: SizeClassType; - renderNonEmoji: RenderTextCallbackType; -}) => ; - -export class MessageBodyHighlight extends React.Component { - public render() { - const { text } = this.props; - const results: Array = []; - const FIND_BEGIN_END = /<>(.+?)<>/g; - - let match = FIND_BEGIN_END.exec(text); - let last = 0; - let count = 1; - - if (!match) { - return ; - } - - const sizeClass = ''; - - while (match) { - if (last < match.index) { - const beforeText = text.slice(last, match.index); - results.push( - renderEmoji({ - text: beforeText, - sizeClass, - key: count++, - renderNonEmoji: renderNewLines, - }) - ); - } - - const [, toHighlight] = match; - results.push( - - {renderEmoji({ - text: toHighlight, - sizeClass, - key: count++, - renderNonEmoji: renderNewLines, - })} - - ); - - // @ts-ignore - last = FIND_BEGIN_END.lastIndex; - match = FIND_BEGIN_END.exec(text); - } - - if (last < text.length) { - results.push( - renderEmoji({ - text: text.slice(last), - sizeClass, - key: count++, - renderNonEmoji: renderNewLines, - }) - ); - } - - return results; - } -} diff --git a/ts/components/search/MessageSearchResults.tsx b/ts/components/search/MessageSearchResults.tsx index 198d69223..7594c8d64 100644 --- a/ts/components/search/MessageSearchResults.tsx +++ b/ts/components/search/MessageSearchResults.tsx @@ -10,7 +10,7 @@ import { import { ContactName } from '../conversation/ContactName'; import { Avatar, AvatarSize } from '../avatar/Avatar'; import { Timestamp } from '../conversation/Timestamp'; -import { MessageBodyHighlight } from '../basic/MessageBodyHighlist'; +import { MessageBodyHighlight } from '../basic/MessageBodyHighlight'; type PropsHousekeeping = { isSelected?: boolean; @@ -29,7 +29,7 @@ export type PropsForSearchResults = { receivedAt?: number; }; -type Props = PropsForSearchResults & PropsHousekeeping; +export type MessageResultProps = PropsForSearchResults & PropsHousekeeping; const FromName = (props: { source: string; destination: string }) => { const { source, destination } = props; @@ -64,7 +64,6 @@ const From = (props: { source: string; destination: string }) => { const ourKey = getOurPubKeyStrFromCache(); - // TODO: ww maybe add useConversationUsername hook within contact name if (destination !== ourKey) { return (
@@ -84,7 +83,7 @@ const AvatarItem = (props: { source: string }) => { return ; }; -export const MessageSearchResult = (props: Props) => { +export const MessageSearchResult = (props: MessageResultProps) => { const { isSelected, id, @@ -96,6 +95,8 @@ export const MessageSearchResult = (props: Props) => { direction, } = props; + // Some messages miss a source or destination. Doing checks to see if the fields can be derived from other sources. + // E.g. if the source is missing but the message is outgoing, the source will be our pubkey const sourceOrDestinationDerivable = (destination && direction === MessageDirection.outgoing) || !destination || diff --git a/ts/components/search/SearchResults.tsx b/ts/components/search/SearchResults.tsx index 8a0b08e5c..c55d487e0 100644 --- a/ts/components/search/SearchResults.tsx +++ b/ts/components/search/SearchResults.tsx @@ -3,13 +3,12 @@ import { ConversationListItemProps, MemoConversationListItemWithDetails, } from '../leftpane/conversation-list-item/ConversationListItem'; -import { MessageSearchResult } from './MessageSearchResults'; +import { MessageResultProps, MessageSearchResult } from './MessageSearchResults'; export type SearchResultsProps = { contacts: Array; conversations: Array; - // TODO: ww add proper typing - messages: Array; + messages: Array; hideMessagesHeader: boolean; searchTerm: string; }; diff --git a/ts/components/settings/section/CategoryAppearance.tsx b/ts/components/settings/section/CategoryAppearance.tsx index 8bac7a476..66c421726 100644 --- a/ts/components/settings/section/CategoryAppearance.tsx +++ b/ts/components/settings/section/CategoryAppearance.tsx @@ -143,8 +143,6 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null title={window.i18n('trimDatabase')} description={window.i18n('trimDatabaseDescription')} onClick={async () => { - console.warn('trim the database to last 10k messages'); - const msgCount = await getMessageCount(); const deleteAmount = Math.max(msgCount - 10000, 0); @@ -156,7 +154,7 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null onClickClose: () => { updateConfirmModal(null); }, - message: `Are you sure you want to delete your ${deleteAmount} oldest received messages?`, + message: window.i18n('trimDatabaseConfirmationBody', [`${deleteAmount}`]), }) ); }} diff --git a/ts/data/data.ts b/ts/data/data.ts index 410865fa0..60e3ab467 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -822,8 +822,7 @@ export async function removeAllMessagesInConversation(conversationId: string): P } export async function trimMessages(): Promise { - const count = await channels.trimMessages(); - console.warn({ count }); + await channels.trimMessages(1000); return; } diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 7a48f859b..de5cffd00 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -29,8 +29,7 @@ type SearchResultsPayloadType = { conversations: Array; contacts: Array; - // TODO: ww typing - messages?: Array; + messages?: Array; }; type SearchResultsKickoffActionType = { diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index da5b86cbc..cf1da1f11 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -464,4 +464,5 @@ export type LocalizerKeys = | 'editGroupName' | 'trimDatabase' | 'trimDatabaseDescription' + | 'trimDatabaseConfirmationBody' | 'reportIssue';