fix: cleanup convo selectors to hope to improve performances

pull/2793/head
Audric Ackermann 2 years ago
parent f2cddb83c8
commit 3eb629e33e

@ -23,13 +23,13 @@ type Props = {
conversationId: string;
};
function submitForOpenGroup(convoId: string, pubkeys: Array<string>) {
async function submitForOpenGroup(convoId: string, pubkeys: Array<string>) {
const convo = getConversationController().get(convoId);
if (!convo || !convo.isPublic()) {
throw new Error('submitForOpenGroup group not found');
}
try {
const roomDetails = SessionUtilUserGroups.getCommunityByConvoIdCached(convo.id);
const roomDetails = await SessionUtilUserGroups.getCommunityByConvoIdNotCached(convo.id);
if (!roomDetails) {
throw new Error(`getCommunityByFullUrl returned no result for ${convo.id}`);
}
@ -133,7 +133,7 @@ const InviteContactsDialogInner = (props: Props) => {
const onClickOK = () => {
if (selectedContacts.length > 0) {
if (isPublicConvo) {
submitForOpenGroup(conversationId, selectedContacts);
void submitForOpenGroup(conversationId, selectedContacts);
} else {
void submitForClosedGroup(conversationId, selectedContacts);
}

@ -26,14 +26,13 @@ const InnerLeftPaneMessageSection = () => {
const searchResults = showSearch ? useSelector(getSearchResults) : undefined;
const lists = showSearch ? undefined : useSelector(getLeftPaneLists);
const conversations = showSearch ? undefined : useSelector(getLeftPaneLists);
const overlayMode = useSelector(getOverlayMode);
return (
// tslint:disable-next-line: use-simple-attributes
<LeftPaneMessageSection
conversations={lists?.conversations || []}
contacts={lists?.contacts || []}
conversations={conversations || []}
searchResults={searchResults}
overlayMode={overlayMode}
/>

@ -1,30 +1,27 @@
import autoBind from 'auto-bind';
import React from 'react';
import { AutoSizer, List, ListRowProps } from 'react-virtualized';
import { SearchResults, SearchResultsProps } from '../search/SearchResults';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { MessageRequestsBanner } from './MessageRequestsBanner';
import {
ConversationListItemProps,
MemoConversationListItemWithDetails,
} from './conversation-list-item/ConversationListItem';
import { ReduxConversationType } from '../../state/ducks/conversations';
import { SearchResults, SearchResultsProps } from '../search/SearchResults';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import autoBind from 'auto-bind';
import _ from 'lodash';
import { MessageRequestsBanner } from './MessageRequestsBanner';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { OverlayMode, setOverlayMode } from '../../state/ducks/section';
import { getOverlayMode } from '../../state/selectors/section';
import { SessionSearchInput } from '../SessionSearchInput';
import { StyledLeftPaneList } from './LeftPaneList';
import { OverlayClosedGroup } from './overlay/OverlayClosedGroup';
import { OverlayCommunity } from './overlay/OverlayCommunity';
import { OverlayMessageRequest } from './overlay/OverlayMessageRequest';
import { OverlayMessage } from './overlay/OverlayMessage';
import { OverlayClosedGroup } from './overlay/OverlayClosedGroup';
import { OverlayMode, setOverlayMode } from '../../state/ducks/section';
import { OverlayMessageRequest } from './overlay/OverlayMessageRequest';
import { OverlayChooseAction } from './overlay/choose-action/OverlayChooseAction';
import styled from 'styled-components';
import { useSelector } from 'react-redux';
import { getOverlayMode } from '../../state/selectors/section';
import { StyledLeftPaneList } from './LeftPaneList';
export interface Props {
contacts: Array<ReduxConversationType>;
conversations?: Array<ConversationListItemProps>;
searchResults?: SearchResultsProps;
overlayMode: OverlayMode | undefined;
@ -84,7 +81,7 @@ export class LeftPaneMessageSection extends React.Component<Props> {
throw new Error('renderRow: conversations selector returned element containing falsy value.');
}
return <MemoConversationListItemWithDetails key={key} style={style} {...conversation} />; // TODO there should not be a need for the ...conversation here?
return <MemoConversationListItemWithDetails key={key} style={style} id={conversation.id} />; // TODO there should not be a need for the ...conversation here?
};
public renderList(): JSX.Element {

@ -73,6 +73,7 @@ import {
} from './sqlInstance';
import { configDumpData } from './sql_calls/config_dump';
import { base64_variants, from_base64, to_hex } from 'libsodium-wrappers-sumo';
import { sleepFor } from '../session/utils/Promise';
// tslint:disable: no-console function-name non-literal-fs-path
@ -184,17 +185,21 @@ async function initializeSql({
// At this point we can allow general access to the database
initDbInstanceWith(db);
await sleepFor(10);
console.info('total message count before cleaning: ', getMessageCount());
console.info('total conversation count before cleaning: ', getConversationCount());
await sleepFor(10);
cleanUpOldOpengroupsOnStart();
cleanUpUnusedNodeForKeyEntriesOnStart();
await sleepFor(10);
printDbStats();
console.info('total message count after cleaning: ', getMessageCount());
console.info('total conversation count after cleaning: ', getConversationCount());
await sleepFor(10);
// Clear any already deleted db entries on each app start.
vacuumDatabase(db);
await sleepFor(10);
} catch (error) {
console.error('error', error);
if (passwordAttempt) {

@ -130,7 +130,7 @@ export class OpenGroupManagerV2 {
return;
}
const inWrapperCommunities = SessionUtilUserGroups.getAllCommunitiesCached();
const inWrapperCommunities = await SessionUtilUserGroups.getAllCommunitiesNotCached();
const inWrapperIds = inWrapperCommunities.map(m =>
getOpenGroupV2ConversationId(m.baseUrl, m.roomCasePreserved)

@ -312,6 +312,7 @@ export class ConversationController {
const load = async () => {
try {
const startLoad = Date.now();
const convoModels = await Data.getAllConversations();
this.conversations.add(convoModels);
@ -324,18 +325,13 @@ export class ConversationController {
switch (variant) {
case 'UserConfig':
case 'UserGroupsConfig':
break;
case 'ContactsConfig':
if (SessionUtilContact.isContactToStoreInWrapper(convo)) {
await SessionUtilContact.refreshMappedValue(convo.id, true);
}
break;
case 'UserGroupsConfig':
if (SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo)) {
await SessionUtilUserGroups.refreshCachedUserGroup(convo.id, true);
}
break;
case 'ConvoInfoVolatileConfig':
if (SessionUtilConvoInfoVolatile.isConvoToStoreInWrapper(convo)) {
await SessionUtilConvoInfoVolatile.refreshConvoVolatileCached(

@ -8,14 +8,8 @@ import {
getLegacyGroupInfoFromDBValues,
} from '../../../types/sqlSharedTypes';
import { UserGroupsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
import { OpenGroupUtils } from '../../apis/open_group_api/utils';
import { getConversationController } from '../../conversations';
/**
* The key of this map is the convoId as stored in the database.
*/
const mappedCommunityWrapperValues = new Map<string, CommunityInfo>();
/**
* Returns true if that conversation is an active group
*/
@ -106,7 +100,6 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
wrapperComm.fullUrl,
wrapperComm.priority
);
await refreshCachedUserGroup(convoId);
} catch (e) {
window.log.warn(`UserGroupsWrapperActions.set of ${convoId} failed with ${e.message}`);
// we still let this go through
@ -134,7 +127,6 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
);
// this does the create or the update of the matching existing legacy group
await UserGroupsWrapperActions.setLegacyGroup(wrapperLegacyGroup);
await refreshCachedUserGroup(convoId);
} catch (e) {
window.log.warn(`UserGroupsWrapperActions.set of ${convoId} failed with ${e.message}`);
// we still let this go through
@ -149,44 +141,18 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
}
}
/**
* @param duringAppStart set this to true if we should just fetch the cached value but not trigger a UI refresh of the corresponding conversation
*/
async function refreshCachedUserGroup(convoId: string, duringAppStart = false) {
try {
let refreshed = false;
if (OpenGroupUtils.isOpenGroupV2(convoId)) {
const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(convoId);
if (fromWrapper && fromWrapper.fullUrlWithPubkey) {
mappedCommunityWrapperValues.set(convoId, fromWrapper);
}
refreshed = true;
}
if (refreshed && !duringAppStart) {
getConversationController()
.get(convoId)
?.triggerUIRefresh();
}
} catch (e) {
window.log.info(`refreshMappedValue: not an opengroup convoID: ${convoId}`, e);
}
// TODOLATER handle the new closed groups once we got them ready
}
function getCommunityByConvoIdCached(convoId: string) {
return mappedCommunityWrapperValues.get(convoId);
async function getCommunityByConvoIdNotCached(convoId: string) {
return UserGroupsWrapperActions.getCommunityByFullUrl(convoId);
}
function getAllCommunitiesCached(): Array<CommunityInfo> {
return [...mappedCommunityWrapperValues.values()];
async function getAllCommunitiesNotCached(): Promise<Array<CommunityInfo>> {
return UserGroupsWrapperActions.getAllCommunities();
}
/**
* Removes the matching community from the wrapper and from the cached list of communities
*/
async function removeCommunityFromWrapper(convoId: string, fullUrlWithOrWithoutPubkey: string) {
async function removeCommunityFromWrapper(_convoId: string, fullUrlWithOrWithoutPubkey: string) {
const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(
fullUrlWithOrWithoutPubkey
);
@ -194,7 +160,6 @@ async function removeCommunityFromWrapper(convoId: string, fullUrlWithOrWithoutP
if (fromWrapper) {
await UserGroupsWrapperActions.eraseCommunityByFullUrl(fromWrapper.fullUrlWithPubkey);
}
mappedCommunityWrapperValues.delete(convoId);
}
/**
@ -226,13 +191,12 @@ export const SessionUtilUserGroups = {
// shared
isUserGroupToStoreInWrapper,
insertGroupsFromDBIntoWrapperAndRefresh,
refreshCachedUserGroup,
getUserGroupTypes,
// communities
isCommunityToStoreInWrapper,
getAllCommunitiesCached,
getCommunityByConvoIdCached,
getAllCommunitiesNotCached,
getCommunityByConvoIdNotCached,
removeCommunityFromWrapper,
// legacy group

@ -237,7 +237,6 @@ export interface ReduxConversationType {
weAreAdmin?: boolean;
unreadCount?: number;
mentionedUs?: boolean;
isSelected?: boolean;
expireTimer?: number;
isTyping?: boolean;

@ -82,17 +82,7 @@ async function doSearch(query: string): Promise<SearchResultsPayloadType> {
const { conversations, contacts } = discussions;
const contactsAndGroups = _.uniq([...conversations, ...contacts]);
const filteredMessages = _.compact(messages);
// if (isAdvancedQuery) {
// const senderFilterQuery =
// advancedSearchOptions.from && advancedSearchOptions.from.length > 0
// ? await queryConversationsAndContacts(advancedSearchOptions.from, options)
// : undefined;
// filteredMessages = advancedFilterMessages(
// filteredMessages,
// advancedSearchOptions,
// senderFilterQuery?.contacts || []
// );
// }
return {
query,
normalizedPhoneNumber: PubKey.normalize(query),

@ -21,11 +21,7 @@ import { MessageTextSelectorProps } from '../../components/conversation/message/
import { GenericReadableMessageSelectorProps } from '../../components/conversation/message/message-item/GenericReadableMessage';
import { LightBoxOptions } from '../../components/conversation/SessionConversation';
import { hasValidIncomingRequestValues } from '../../models/conversation';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
isOpenOrClosedGroup,
} from '../../models/conversationAttributes';
import { CONVERSATION_PRIORITIES, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import { getConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils';
import { LocalizerType } from '../../types/Util';
@ -252,29 +248,60 @@ export const getConversationComparator = createSelector(getIntl, _getConversatio
// tslint:disable-next-line: cyclomatic-complexity
const _getLeftPaneLists = (
sortedConversations: Array<ReduxConversationType>
): {
conversations: Array<ReduxConversationType>;
contacts: Array<ReduxConversationType>;
globalUnreadCount: number;
} => {
const conversations: Array<ReduxConversationType> = [];
const directConversations: Array<ReduxConversationType> = [];
): Array<ReduxConversationType> => {
return sortedConversations.filter(conversation => {
if (conversation.isBlocked) return false;
// a private conversation not approved is a message request. Exclude them from the left pane lists
if (conversation.isPrivate && !conversation.isApproved) {
return false;
}
const isPrivateButHidden =
conversation.isPrivate &&
conversation.priority &&
conversation.priority <= CONVERSATION_PRIORITIES.default;
/**
* When getting a contact from a linked device, before he sent a message, the approved field is false, but a createdAt is used as activeAt
*/
const isPrivateUnapprovedButActive =
conversation.isPrivate && !conversation.isApproved && !conversation.activeAt;
if (
isPrivateUnapprovedButActive ||
isPrivateButHidden // a hidden contact conversation is only visible from the contact list, not from the global conversation list
) {
// dont increase unread counter, don't push to convo list.
return false;
}
return true;
});
};
// tslint:disable-next-line: cyclomatic-complexity
const _getPrivateFriendsConversations = (
sortedConversations: Array<ReduxConversationType>
): Array<ReduxConversationType> => {
return sortedConversations.filter(convo => {
convo.isPrivate &&
!convo.isMe &&
!convo.isBlocked &&
convo.isApproved &&
convo.didApproveMe &&
convo.activeAt !== undefined;
});
};
// tslint:disable-next-line: cyclomatic-complexity
const _getGlobalUnreadCount = (sortedConversations: Array<ReduxConversationType>): number => {
let globalUnreadCount = 0;
for (const conversation of sortedConversations) {
// Blocked conversation are now only visible from the settings, not in the conversation list, so don't add it neither to the contacts list nor the conversation list
if (conversation.isBlocked) {
continue;
}
// a contact is a private conversation that is approved by us and active
if (
conversation.activeAt !== undefined &&
conversation.type === ConversationTypeEnum.PRIVATE &&
conversation.isApproved
// we want to keep the hidden conversation in the direct contact list, so we don't filter based on priority
) {
directConversations.push(conversation);
}
// a private conversation not approved is a message request. Exclude them from the left pane lists
if (conversation.isPrivate && !conversation.isApproved) {
@ -309,21 +336,14 @@ const _getLeftPaneLists = (
) {
globalUnreadCount += conversation.unreadCount;
}
conversations.push(conversation);
}
return {
conversations,
contacts: directConversations,
globalUnreadCount,
};
return globalUnreadCount;
};
export const _getSortedConversations = (
lookup: ConversationLookupType,
comparator: (left: ReduxConversationType, right: ReduxConversationType) => number,
selectedConversation?: string
comparator: (left: ReduxConversationType, right: ReduxConversationType) => number
): Array<ReduxConversationType> => {
const values = Object.values(lookup);
const sorted = values.sort(comparator);
@ -338,11 +358,9 @@ export const _getSortedConversations = (
}
const isBlocked = BlockedNumberController.isBlocked(conversation.id);
const isSelected = selectedConversation === conversation.id;
sortedConversations.push({
...conversation,
isSelected: isSelected || undefined,
isBlocked: isBlocked || undefined,
});
}
@ -397,21 +415,6 @@ export const getUnreadConversationRequests = createSelector(
_getUnreadConversationRequests
);
const _getPrivateContactsPubkeys = (
sortedConversations: Array<ReduxConversationType>
): Array<string> => {
return filter(sortedConversations, conversation => {
return !!(
conversation.isPrivate &&
!conversation.isBlocked &&
!conversation.isMe &&
conversation.didApproveMe &&
conversation.isApproved &&
conversation.activeAt
);
}).map(convo => convo.id);
};
/**
* Returns all the conversation ids of private conversations which are
* - private
@ -420,20 +423,15 @@ const _getPrivateContactsPubkeys = (
* - approved (or message requests are disabled)
* - active_at is set to something truthy
*/
export const getPrivateContactsPubkeys = createSelector(
getSortedConversations,
_getPrivateContactsPubkeys
export const getPrivateContactsPubkeys = createSelector(_getPrivateFriendsConversations, state =>
state.map(m => m.id)
);
export const getLeftPaneLists = createSelector(getSortedConversations, _getLeftPaneLists);
export const getDirectContacts = createSelector(
getLeftPaneLists,
(state: {
conversations: Array<ReduxConversationType>;
contacts: Array<ReduxConversationType>;
globalUnreadCount: number;
}) => state.contacts
getSortedConversations,
_getPrivateFriendsConversations
);
export const getDirectContactsCount = createSelector(
@ -472,9 +470,10 @@ export const getDirectContactsByName = createSelector(
}
);
export const getGlobalUnreadMessageCount = createSelector(getLeftPaneLists, (state): number => {
return state.globalUnreadCount;
});
export const getGlobalUnreadMessageCount = createSelector(
getSortedConversations,
_getGlobalUnreadCount
);
export const isMessageDetailView = (state: StateType): boolean =>
state.conversations.messageDetailProps !== undefined;

@ -1,12 +1,11 @@
import { compact } from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import { compact } from 'lodash';
import { StateType } from '../reducer';
import { ConversationLookupType } from '../ducks/conversations';
import { SearchStateType } from '../ducks/search';
import { getConversationLookup } from './conversations';
import { ConversationLookupType } from '../ducks/conversations';
import { getSelectedConversationKey } from './selectedConversation';
export const getSearch = (state: StateType): SearchStateType => state.search;
@ -17,33 +16,24 @@ export const isSearching = (state: StateType) => {
};
export const getSearchResults = createSelector(
[getSearch, getConversationLookup, getSelectedConversationKey],
(searchState: SearchStateType, lookup: ConversationLookupType, selectedConversation?: string) => {
[getSearch, getConversationLookup],
(searchState: SearchStateType, lookup: ConversationLookupType) => {
return {
contactsAndGroups: compact(
searchState.contactsAndGroups.map(id => {
const value = lookup[id];
// on some edges cases, we have an id but no corresponding convo because it matches a query but the conversation was removed.
if (!value) {
return null;
}
// Don't return anything when activeAt is unset (i.e. no current conversations with this user)
if (value.activeAt === undefined || value.activeAt === 0) {
//activeAt can be 0 when linking device
return null;
}
if (value && id === selectedConversation) {
return {
...value,
isSelected: true,
};
}
return value;
})
searchState.contactsAndGroups
.filter(id => {
const value = lookup[id];
// on some edges cases, we have an id but no corresponding convo because it matches a query but the conversation was removed.
// Don't return anything when activeAt is unset (i.e. no current conversations with this user)
if (!value || value.activeAt === undefined || value.activeAt === 0) {
//activeAt can be 0 when linking device
return false;
}
return true;
})
.map(id => lookup[id])
),
messages: compact(searchState.messages),
searchTerm: searchState.query,

@ -24,7 +24,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -49,7 +48,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -73,7 +71,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -97,7 +94,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -121,7 +117,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -163,7 +158,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -189,7 +183,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -215,7 +208,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -241,7 +233,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,
@ -266,7 +257,6 @@ describe('state/selectors/conversations', () => {
isMe: false,
unreadCount: 1,
mentionedUs: false,
isSelected: false,
isTyping: false,
isBlocked: false,
isKickedFromGroup: false,

@ -152,7 +152,6 @@ export const processNewAttachment = async (attachment: {
const rotatedData = await autoOrientJPEGAttachment(attachment);
const onDiskAttachmentPath = await migrateDataToFileSystem(rotatedData.data);
const attachmentWithoutData = omit({ ...attachment, fileName, path: onDiskAttachmentPath }, [
'data',
]);

@ -157,6 +157,7 @@ export async function autoScale<T extends { contentType: string; blob: Blob }>(
width?: number;
height?: number;
}> {
const start = Date.now();
const { contentType, blob } = attachment;
if (contentType.split('/')[0] !== 'image' || contentType === IMAGE_TIFF) {
// nothing to do
@ -238,7 +239,6 @@ export async function autoScale<T extends { contentType: string; blob: Blob }>(
let quality = 0.95;
const startI = 4;
let i = startI;
// const start = Date.now();
do {
i -= 1;
if (DEBUG_ATTACHMENTS_SCALE) {
@ -260,7 +260,7 @@ export async function autoScale<T extends { contentType: string; blob: Blob }>(
if (readAndResizedBlob.size > maxSize) {
throw new Error('Cannot add this attachment even after trying to scale it down.');
}
// window.log.debug(`[perf] autoscale took ${Date.now() - start}ms `);
window.log.debug(`[perf] autoscale took ${Date.now() - start}ms `);
return {
contentType: attachment.contentType,

Loading…
Cancel
Save