Limit unnecessary thumbnail generation

This commit is contained in:
trevor-signal 2025-09-24 10:55:08 -04:00 committed by GitHub
commit 74e327a6c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 132 additions and 81 deletions

View file

@ -7,7 +7,7 @@ import * as durations from '../util/durations/index.js';
import { createLogger } from '../logging/log.js';
import type { AttachmentBackfillResponseSyncEvent } from '../textsecure/messageReceiverEvents.js';
import {
type AttachmentDownloadJobTypeType,
type MessageAttachmentType,
type AttachmentDownloadJobType,
type CoreAttachmentDownloadJobType,
AttachmentDownloadUrgency,
@ -82,7 +82,7 @@ export { isPermanentlyUndownloadable };
// Type for adding a new job
export type NewAttachmentDownloadJobType = {
attachment: AttachmentType;
attachmentType: AttachmentDownloadJobTypeType;
attachmentType: MessageAttachmentType;
isManualDownload: boolean;
messageId: string;
receivedAt: number;
@ -808,10 +808,13 @@ export async function runDownloadAttachmentJobInner({
},
});
const upgradedAttachment = await dependencies.processNewAttachment({
...omit(attachment, ['error', 'pending']),
...downloadedAttachment,
});
const upgradedAttachment = await dependencies.processNewAttachment(
{
...omit(attachment, ['error', 'pending']),
...downloadedAttachment,
},
attachmentType
);
const isShowingLightbox = (): boolean => {
const lightboxState = window.reduxStore.getState().lightbox;

View file

@ -13,7 +13,7 @@ import {
getUndownloadedAttachmentSignature,
} from '../../types/Attachment.js';
import {
type AttachmentDownloadJobTypeType,
type MessageAttachmentType,
AttachmentDownloadUrgency,
} from '../../types/AttachmentDownload.js';
import { AttachmentDownloadSource } from '../../sql/Interface.js';
@ -353,7 +353,7 @@ export class AttachmentBackfill {
}
public static isEnabledForJob(
jobType: AttachmentDownloadJobTypeType,
jobType: MessageAttachmentType,
message: Pick<ReadonlyMessageAttributesType, 'type'>
): boolean {
if (message.type === 'story') {
@ -456,7 +456,7 @@ export class AttachmentBackfill {
export function isPermanentlyUndownloadable(
attachment: AttachmentType,
disposition: AttachmentDownloadJobTypeType,
disposition: MessageAttachmentType,
message: Pick<ReadonlyMessageAttributesType, 'type'>
): boolean {
// Attachment is downloadable or user have not failed to download it yet

View file

@ -3,7 +3,7 @@
import lodash from 'lodash';
import { createLogger } from '../logging/log.js';
import * as Bytes from '../Bytes.js';
import type { AttachmentDownloadJobTypeType } from '../types/AttachmentDownload.js';
import type { MessageAttachmentType } from '../types/AttachmentDownload.js';
import type { AttachmentType } from '../types/Attachment.js';
import {
@ -69,7 +69,7 @@ export async function addAttachmentToMessage(
messageId: string,
attachment: AttachmentType,
jobLogId: string,
{ type }: { type: AttachmentDownloadJobTypeType }
{ type }: { type: MessageAttachmentType }
): Promise<void> {
const logPrefix = `${jobLogId}/addAttachmentToMessage`;
const message = await getMessageById(messageId);

View file

@ -245,10 +245,13 @@ export class ReleaseNotesFetcher {
);
const processedAttachment =
await window.Signal.Migrations.processNewAttachment({
...localAttachment,
contentType: stringToMIMEType(contentType),
});
await window.Signal.Migrations.processNewAttachment(
{
...localAttachment,
contentType: stringToMIMEType(contentType),
},
'attachment'
);
return { hydratedNote, processedAttachment };
}

View file

@ -58,6 +58,7 @@ import type {
} from './types/message/LinkPreviews.js';
import type { StickerType, StickerWithHydratedData } from './types/Stickers.js';
import { beforeNavigateService } from './services/BeforeNavigate.js';
import type { MessageAttachmentType } from './types/AttachmentDownload.js';
type EncryptedReader = (
attachment: Partial<AddressableAttachmentType>
@ -122,7 +123,10 @@ type MigrationsModuleType = {
name: string;
baseDir?: string;
}) => Promise<null | { fullPath: string; name: string }>;
processNewAttachment: (attachment: AttachmentType) => Promise<AttachmentType>;
processNewAttachment: (
attachment: AttachmentType,
attachmentType: MessageAttachmentType
) => Promise<AttachmentType>;
processNewSticker: (stickerData: Uint8Array) => Promise<
LocalAttachmentV2Type & {
width: number;
@ -327,8 +331,11 @@ export function initializeMigrations({
readStickerData,
readTempData,
saveAttachmentToDisk,
processNewAttachment: (attachment: AttachmentType) =>
MessageType.processNewAttachment(attachment, {
processNewAttachment: (
attachment: AttachmentType,
attachmentType: MessageAttachmentType
) =>
MessageType.processNewAttachment(attachment, attachmentType, {
writeNewAttachmentData,
makeObjectUrl,
revokeObjectUrl,

View file

@ -49,7 +49,7 @@ import type {
} from '../types/CallLink.js';
import type {
AttachmentDownloadJobType,
AttachmentDownloadJobTypeType,
MessageAttachmentType,
} from '../types/AttachmentDownload.js';
import type {
GroupSendEndorsementsData,
@ -651,7 +651,7 @@ export const MESSAGE_ATTACHMENT_COLUMNS = [
export type MessageAttachmentDBType = {
messageId: string;
attachmentType: AttachmentDownloadJobTypeType;
attachmentType: MessageAttachmentType;
orderInMessage: number;
editHistoryIndex: number | null;
conversationId: string;

View file

@ -86,7 +86,7 @@ import {
} from '../types/AttachmentBackup.js';
import {
attachmentDownloadJobSchema,
type AttachmentDownloadJobTypeType,
type MessageAttachmentType,
type AttachmentDownloadJobType,
} from '../types/AttachmentDownload.js';
import type {
@ -2700,7 +2700,7 @@ function saveMessageAttachment({
sentAt: number;
receivedAt: number;
receivedAtMs: number | undefined;
attachmentType: AttachmentDownloadJobTypeType;
attachmentType: MessageAttachmentType;
attachment: AttachmentType;
orderInMessage: number;
editHistoryIndex: number | null;

View file

@ -6,9 +6,9 @@ import * as z from 'zod';
import type { LoggerType } from '../../types/Logging.js';
import {
attachmentDownloadTypeSchema,
messageAttachmentTypeSchema,
type AttachmentDownloadJobType,
type AttachmentDownloadJobTypeType,
type MessageAttachmentType,
} from '../../types/AttachmentDownload.js';
import type { AttachmentType } from '../../types/Attachment.js';
import { jsonToObject, objectToJSON, sql } from '../util.js';
@ -28,7 +28,7 @@ export type _AttachmentDownloadJobTypeV1030 = {
messageId: string;
pending: number;
timestamp: number;
type: AttachmentDownloadJobTypeType;
type: MessageAttachmentType;
};
const attachmentDownloadJobSchemaV1040 = z
@ -36,7 +36,7 @@ const attachmentDownloadJobSchemaV1040 = z
attachment: z
.object({ size: z.number(), contentType: MIMETypeSchema })
.passthrough(),
attachmentType: attachmentDownloadTypeSchema,
attachmentType: messageAttachmentTypeSchema,
ciphertextSize: z.number(),
contentType: MIMETypeSchema,
digest: z.string(),

View file

@ -3,7 +3,7 @@
import { z } from 'zod';
import { convertUndefinedToNull } from '../../util/dropNull.js';
import { attachmentDownloadTypeSchema } from '../../types/AttachmentDownload.js';
import { messageAttachmentTypeSchema } from '../../types/AttachmentDownload.js';
import { APPLICATION_OCTET_STREAM } from '../../types/MIME.js';
import type { MessageAttachmentDBType } from '../Interface.js';
@ -35,7 +35,7 @@ export const permissiveMessageAttachmentSchema = z.object({
messageId: z.string(),
messageType: z.string(),
editHistoryIndex: z.number(),
attachmentType: attachmentDownloadTypeSchema,
attachmentType: messageAttachmentTypeSchema,
orderInMessage: z.number(),
conversationId: z.string(),
sentAt: z.number().catch(0),

View file

@ -71,7 +71,7 @@ import {
isIncremental,
defaultBlurHash,
} from '../../types/Attachment.js';
import type { AttachmentDownloadJobTypeType } from '../../types/AttachmentDownload.js';
import type { MessageAttachmentType } from '../../types/AttachmentDownload.js';
import { type DefaultConversationColorType } from '../../types/Colors.js';
import { ReadStatus } from '../../messages/MessageReadStatus.js';
@ -1852,7 +1852,7 @@ export function getPropsForEmbeddedContact(
export function getPropsForAttachment(
attachment: AttachmentType,
disposition: AttachmentDownloadJobTypeType,
disposition: MessageAttachmentType,
message: Pick<ReadonlyMessageAttributesType, 'type'>
): AttachmentForUIType {
const { path, pending, screenshot, thumbnail, thumbnailFromBackup } =

View file

@ -37,6 +37,7 @@ import {
} from './Crypto.js';
import { missingCaseError } from '../util/missingCaseError.js';
import type { MakeVideoScreenshotResultType } from './VisualAttachment.js';
import type { MessageAttachmentType } from './AttachmentDownload.js';
const {
isNumber,
@ -482,6 +483,7 @@ const THUMBNAIL_CONTENT_TYPE = MIME.IMAGE_PNG;
export async function captureDimensionsAndScreenshot(
attachment: AttachmentType,
options: { generateThumbnail: boolean },
params: {
writeNewAttachmentData: (
data: Uint8Array
@ -544,28 +546,35 @@ export async function captureDimensionsAndScreenshot(
objectUrl: localUrl,
logger,
});
const thumbnailBuffer = await blobToArrayBuffer(
await makeImageThumbnail({
size: THUMBNAIL_SIZE,
objectUrl: localUrl,
contentType: THUMBNAIL_CONTENT_TYPE,
logger,
})
);
let thumbnail: LocalAttachmentV2Type | undefined;
if (options.generateThumbnail) {
const thumbnailBuffer = await blobToArrayBuffer(
await makeImageThumbnail({
size: THUMBNAIL_SIZE,
objectUrl: localUrl,
contentType: THUMBNAIL_CONTENT_TYPE,
logger,
})
);
thumbnail = await writeNewAttachmentData(
new Uint8Array(thumbnailBuffer)
);
}
const thumbnail = await writeNewAttachmentData(
new Uint8Array(thumbnailBuffer)
);
return {
...attachment,
width,
height,
thumbnail: {
...thumbnail,
contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE,
},
thumbnail: thumbnail
? {
...thumbnail,
contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE,
}
: undefined,
};
} catch (error) {
logger.error(
@ -597,18 +606,19 @@ export async function captureDimensionsAndScreenshot(
new Uint8Array(screenshotBuffer)
);
const thumbnailBuffer = await blobToArrayBuffer(
await makeImageThumbnail({
size: THUMBNAIL_SIZE,
objectUrl: screenshotObjectUrl,
contentType: THUMBNAIL_CONTENT_TYPE,
logger,
})
);
let thumbnail: LocalAttachmentV2Type | undefined;
if (options.generateThumbnail) {
const thumbnailBuffer = await blobToArrayBuffer(
await makeImageThumbnail({
size: THUMBNAIL_SIZE,
objectUrl: screenshotObjectUrl,
contentType: THUMBNAIL_CONTENT_TYPE,
logger,
})
);
const thumbnail = await writeNewAttachmentData(
new Uint8Array(thumbnailBuffer)
);
thumbnail = await writeNewAttachmentData(new Uint8Array(thumbnailBuffer));
}
return {
...attachment,
@ -619,12 +629,14 @@ export async function captureDimensionsAndScreenshot(
width,
height,
},
thumbnail: {
...thumbnail,
contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE,
},
thumbnail: thumbnail
? {
...thumbnail,
contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE,
}
: undefined,
width,
height,
};
@ -1421,3 +1433,12 @@ export function partitionBodyAndNormalAttachments<
attachments: normalAttachments,
};
}
const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS: Set<MessageAttachmentType> =
new Set(['attachment', 'sticker']);
export function shouldGenerateThumbnailForAttachmentType(
type: MessageAttachmentType
): boolean {
return MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS.has(type);
}

View file

@ -14,7 +14,7 @@ export enum MediaTier {
BACKUP = 'backup',
}
export const attachmentDownloadTypeSchema = z.enum([
export const messageAttachmentTypeSchema = z.enum([
'long-message',
'attachment',
'preview',
@ -23,13 +23,11 @@ export const attachmentDownloadTypeSchema = z.enum([
'sticker',
]);
export type AttachmentDownloadJobTypeType = z.infer<
typeof attachmentDownloadTypeSchema
>;
export type MessageAttachmentType = z.infer<typeof messageAttachmentTypeSchema>;
export type CoreAttachmentDownloadJobType = {
attachment: AttachmentType;
attachmentType: AttachmentDownloadJobTypeType;
attachmentType: MessageAttachmentType;
ciphertextSize: number;
contentType: MIMEType;
attachmentSignature: string;
@ -49,7 +47,7 @@ export const coreAttachmentDownloadJobSchema = z.object({
attachment: z
.object({ size: z.number(), contentType: MIMETypeSchema })
.passthrough(),
attachmentType: attachmentDownloadTypeSchema,
attachmentType: messageAttachmentTypeSchema,
ciphertextSize: z.number(),
contentType: MIMETypeSchema,
attachmentSignature: z.string(),

View file

@ -16,6 +16,7 @@ import {
removeSchemaVersion,
replaceUnicodeOrderOverrides,
replaceUnicodeV2,
shouldGenerateThumbnailForAttachmentType,
} from './Attachment.js';
import type { MakeVideoScreenshotResultType } from './VisualAttachment.js';
import * as Errors from './errors.js';
@ -48,6 +49,7 @@ import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment.js';
import { deepClone } from '../util/deepClone.js';
import * as Bytes from '../Bytes.js';
import { isBodyTooLong } from '../util/longAttachment.js';
import type { MessageAttachmentType } from './AttachmentDownload.js';
const { isFunction, isObject, identity } = lodash;
@ -493,7 +495,13 @@ const toVersion7 = _withSchemaVersion({
const toVersion8 = _withSchemaVersion({
schemaVersion: 8,
upgrade: _mapAttachments(captureDimensionsAndScreenshot),
upgrade: _mapAttachments((attachment, context) =>
captureDimensionsAndScreenshot(
attachment,
{ generateThumbnail: true },
context
)
),
});
const toVersion9 = _withSchemaVersion({
@ -768,6 +776,7 @@ export const upgradeSchema = async (
// downloaded out of band.
export const processNewAttachment = async (
attachment: AttachmentType,
attachmentType: MessageAttachmentType,
{
writeNewAttachmentData,
makeObjectUrl,
@ -810,15 +819,22 @@ export const processNewAttachment = async (
throw new TypeError('context.logger is required');
}
const finalAttachment = await captureDimensionsAndScreenshot(attachment, {
writeNewAttachmentData,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
});
const finalAttachment = await captureDimensionsAndScreenshot(
attachment,
{
generateThumbnail:
shouldGenerateThumbnailForAttachmentType(attachmentType),
},
{
writeNewAttachmentData,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
}
);
return finalAttachment;
};

View file

@ -73,7 +73,10 @@ export async function downloadOnboardingStory(): Promise<void> {
...local,
};
return window.Signal.Migrations.processNewAttachment(attachment);
return window.Signal.Migrations.processNewAttachment(
attachment,
'attachment'
);
})
);