Skip to content

Commit 132737b

Browse files
authored
Merge pull request #1669 from session-foundation/fix-download-avatar-track-reupload-neede
fix: track reupload needed for incoming avatar for us
2 parents 5b53ebd + 5d0e724 commit 132737b

File tree

6 files changed

+44
-16
lines changed

6 files changed

+44
-16
lines changed

ts/session/apis/seed_node_api/SeedNodeAPI.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,23 +208,21 @@ export async function TEST_fetchSnodePoolFromSeedNodeRetryable(
208208
throw new Error('fetchSnodePoolFromSeedNodeRetryable: Seed nodes are empty');
209209
}
210210

211-
const seedNodeUrl = _.sample(seedNodes);
212-
if (!seedNodeUrl) {
211+
if (!seedNodes.length) {
213212
window?.log?.warn(
214-
'loki_snode_api::fetchSnodePoolFromSeedNodeRetryable - Could not select random snodes from',
213+
'loki_snode_api::fetchSnodePoolFromSeedNodeRetryable - no seednodes',
215214
seedNodes
216215
);
217216
throw new Error('fetchSnodePoolFromSeedNodeRetryable: Seed nodes are empty #2');
218217
}
219218

220-
const tryUrl = new URL(seedNodeUrl);
219+
const snodes = await Promise.race(seedNodes.map(s => getSnodesFromSeedUrl(new URL(s))));
221220

222-
const snodes = await getSnodesFromSeedUrl(tryUrl);
223221
if (snodes.length === 0) {
224222
window?.log?.warn(
225-
`loki_snode_api::fetchSnodePoolFromSeedNodeRetryable - ${seedNodeUrl} did not return any snodes`
223+
`loki_snode_api::fetchSnodePoolFromSeedNodeRetryable - Promise.race did not return any snodes`
226224
);
227-
throw new Error(`Failed to contact seed node: ${seedNodeUrl}`);
225+
throw new Error(`Failed to contact seed node: Promise.race did not return any snodes`);
228226
}
229227

230228
return snodes;

ts/session/utils/job_runners/jobs/AvatarDownloadJob.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class AvatarDownloadJob extends PersistedJob<AvatarDownloadPersistedData> {
164164
);
165165

166166
// we autoscale incoming avatars because our app keeps decrypted avatars in memory and some platforms allows large avatars to be uploaded.
167-
const processed = await processAvatarData(decryptedData, conversation.isMe());
167+
const processed = await processAvatarData(decryptedData, conversation.isMe(), true);
168168

169169
const upgradedMainAvatar = await processNewAttachment({
170170
data: processed.mainAvatarDetails.outputBuffer,

ts/session/utils/job_runners/jobs/AvatarMigrateJob.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class AvatarMigrateJob extends PersistedJob<AvatarMigratePersistedData> {
141141
}
142142

143143
// we autoscale incoming avatars because our app keeps decrypted avatars in memory and some platforms allows large avatars to be uploaded.
144-
const processed = await processAvatarData(decryptedData, conversation.isMe());
144+
const processed = await processAvatarData(decryptedData, conversation.isMe(), true);
145145

146146
const upgradedMainAvatar = await processNewAttachment({
147147
data: processed.mainAvatarDetails.outputBuffer,

ts/util/avatar/processAvatarData.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants';
1616
* This is because we need to be able to reupload our full avatar to the file server, and mobile pixel density can be 3x.
1717
* We still want to reduce incoming avatars to 200 x 200 for performance reasons.
1818
*/
19-
export async function processAvatarData(arrayBuffer: ArrayBuffer, planForReupload: boolean) {
19+
export async function processAvatarData(
20+
arrayBuffer: ArrayBuffer,
21+
planForReupload: boolean,
22+
remoteChange = false
23+
) {
2024
if (!arrayBuffer || arrayBuffer.byteLength === 0 || !isArrayBuffer(arrayBuffer)) {
2125
throw new Error('processAvatarData: arrayBuffer is empty');
2226
}
@@ -27,7 +31,11 @@ export async function processAvatarData(arrayBuffer: ArrayBuffer, planForReuploa
2731
* 2. a fallback avatar in case the user looses its pro (static image, even if the main avatar is animated)
2832
*/
2933
// this is step 1, we generate a scaled down avatar, but keep its nature (animated or not)
30-
const processed = await ImageProcessor.processAvatarData(arrayBuffer, planForReupload);
34+
const processed = await ImageProcessor.processAvatarData(
35+
arrayBuffer,
36+
planForReupload,
37+
remoteChange
38+
);
3139

3240
if (!processed) {
3341
throw new Error('processLocalAvatarChange: failed to process avatar');

ts/webworker/workers/node/image_processor/image_processor.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export type ImageProcessorWorkerActions = {
6161
*/
6262
processAvatarData: (
6363
input: ArrayBufferLike,
64-
planForReupload: boolean
64+
planForReupload: boolean,
65+
remoteChange: boolean
6566
) => Promise<{
6667
mainAvatarDetails: Omit<MaybeAnimatedOutputType, 'format'> & WithImageFormat<'gif' | 'webp'>;
6768
avatarFallback: (StaticOutputType & WithWebpFormat) | null;

ts/webworker/workers/node/image_processor/image_processor.worker.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,32 @@ async function sleepFor(ms: number) {
264264
});
265265
}
266266

267-
async function processPlanForReuploadAvatar({ inputBuffer }: { inputBuffer: ArrayBufferLike }) {
267+
async function processPlanForReuploadAvatar({
268+
inputBuffer,
269+
remoteChange,
270+
}: {
271+
inputBuffer: ArrayBufferLike;
272+
remoteChange: boolean;
273+
}) {
268274
const start = Date.now();
269275

270276
const metadata = await metadataFromBuffer(inputBuffer, true, { animated: true });
271277
if (!metadata) {
272278
return null;
273279
}
274280

275-
const sizeRequired = maxAvatarDetails.maxSidePlanReupload;
281+
/**
282+
* This is not pretty, but when we download our own avatar from the network and we didn't set it locally,
283+
* we need to make sure a reupload will be planned if required.
284+
* What this means is that, if we get an avatar of size 640 from the network we should plan for a reupload.
285+
* But, if we resize it here to 600, the AvatarReuploadJob will be skipped as the avatar is already the correct size.
286+
* As a hack, we add 1 pixel to the size required when this is a remote change, so that the AvatarReuploadJob will be triggered.
287+
*
288+
* Note: We do not upscale the file if it's already smaller than 600px, so a reupload won't be triggered if a device set an avatar to 600 already.
289+
*/
290+
const sizeRequired = remoteChange
291+
? maxAvatarDetails.maxSidePlanReupload + 1
292+
: maxAvatarDetails.maxSidePlanReupload;
276293
const avatarIsAnimated = isAnimated(metadata);
277294

278295
if (avatarIsAnimated && metadata.format !== 'webp' && metadata.format !== 'gif') {
@@ -523,13 +540,17 @@ const workerActions: ImageProcessorWorkerActions = {
523540
};
524541
},
525542

526-
processAvatarData: async (inputBuffer: ArrayBufferLike, planForReupload: boolean) => {
543+
processAvatarData: async (
544+
inputBuffer: ArrayBufferLike,
545+
planForReupload: boolean,
546+
remoteChange: boolean
547+
) => {
527548
if (!inputBuffer?.byteLength) {
528549
throw new Error('processAvatarData: inputBuffer is required');
529550
}
530551

531552
if (planForReupload) {
532-
return await processPlanForReuploadAvatar({ inputBuffer });
553+
return await processPlanForReuploadAvatar({ inputBuffer, remoteChange });
533554
}
534555
return await processNoPlanForReuploadAvatar({ inputBuffer });
535556
},

0 commit comments

Comments
 (0)