Allow backfill for more undownloadable attachments
This commit is contained in:
@@ -12,10 +12,7 @@ import {
|
|||||||
AttachmentDownloadUrgency,
|
AttachmentDownloadUrgency,
|
||||||
coreAttachmentDownloadJobSchema,
|
coreAttachmentDownloadJobSchema,
|
||||||
} from '../types/AttachmentDownload';
|
} from '../types/AttachmentDownload';
|
||||||
import {
|
import { downloadAttachment as downloadAttachmentUtil } from '../util/downloadAttachment';
|
||||||
AttachmentPermanentlyUndownloadableError,
|
|
||||||
downloadAttachment as downloadAttachmentUtil,
|
|
||||||
} from '../util/downloadAttachment';
|
|
||||||
import { DataWriter } from '../sql/Client';
|
import { DataWriter } from '../sql/Client';
|
||||||
import { getValue } from '../RemoteConfig';
|
import { getValue } from '../RemoteConfig';
|
||||||
|
|
||||||
@@ -24,6 +21,7 @@ import {
|
|||||||
AttachmentSizeError,
|
AttachmentSizeError,
|
||||||
type AttachmentType,
|
type AttachmentType,
|
||||||
AttachmentVariant,
|
AttachmentVariant,
|
||||||
|
AttachmentPermanentlyUndownloadableError,
|
||||||
mightBeOnBackupTier,
|
mightBeOnBackupTier,
|
||||||
} from '../types/Attachment';
|
} from '../types/Attachment';
|
||||||
import { type ReadonlyMessageAttributesType } from '../model-types.d';
|
import { type ReadonlyMessageAttributesType } from '../model-types.d';
|
||||||
@@ -61,6 +59,7 @@ import { markAttachmentAsPermanentlyErrored } from '../util/attachments/markAtta
|
|||||||
import {
|
import {
|
||||||
AttachmentBackfill,
|
AttachmentBackfill,
|
||||||
isPermanentlyUndownloadable,
|
isPermanentlyUndownloadable,
|
||||||
|
isPermanentlyUndownloadableWithoutBackfill,
|
||||||
} from './helpers/attachmentBackfill';
|
} from './helpers/attachmentBackfill';
|
||||||
|
|
||||||
export { isPermanentlyUndownloadable };
|
export { isPermanentlyUndownloadable };
|
||||||
@@ -365,6 +364,16 @@ async function runDownloadAttachmentJob({
|
|||||||
try {
|
try {
|
||||||
log.info(`${logId}: Starting job`);
|
log.info(`${logId}: Starting job`);
|
||||||
|
|
||||||
|
if (
|
||||||
|
job.source !== AttachmentDownloadSource.BACKFILL &&
|
||||||
|
isPermanentlyUndownloadableWithoutBackfill(job.attachment)
|
||||||
|
) {
|
||||||
|
// We should only get to here only if
|
||||||
|
throw new AttachmentPermanentlyUndownloadableError(
|
||||||
|
'Not downloadable without backfill'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await runDownloadAttachmentJobInner({
|
const result = await runDownloadAttachmentJobInner({
|
||||||
job,
|
job,
|
||||||
abortSignal: options.abortSignal,
|
abortSignal: options.abortSignal,
|
||||||
|
@@ -113,17 +113,6 @@ export class AttachmentBackfill {
|
|||||||
AttachmentData: { Status },
|
AttachmentData: { Status },
|
||||||
} = Proto.SyncMessage.AttachmentBackfillResponse;
|
} = Proto.SyncMessage.AttachmentBackfillResponse;
|
||||||
|
|
||||||
if ('error' in response) {
|
|
||||||
if (response.error === ErrorEnum.MESSAGE_NOT_FOUND) {
|
|
||||||
window.reduxActions.globalModals.showBackfillFailureModal({
|
|
||||||
kind: BackfillFailureKind.NotFound,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw missingCaseError(response.error);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { targetMessage, targetConversation } = response;
|
const { targetMessage, targetConversation } = response;
|
||||||
|
|
||||||
const convo = getConversationFromTarget(targetConversation);
|
const convo = getConversationFromTarget(targetConversation);
|
||||||
@@ -141,13 +130,29 @@ export class AttachmentBackfill {
|
|||||||
|
|
||||||
const message = window.MessageCache.register(new MessageModel(attributes));
|
const message = window.MessageCache.register(new MessageModel(attributes));
|
||||||
|
|
||||||
// IMPORTANT: no awaits until we finish modifying attachments
|
|
||||||
|
|
||||||
const timer = this.#pendingRequests.get(message.id);
|
const timer = this.#pendingRequests.get(message.id);
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('error' in response) {
|
||||||
|
// Don't show error if we didn't request the data or already timed out
|
||||||
|
if (timer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.error === ErrorEnum.MESSAGE_NOT_FOUND) {
|
||||||
|
window.reduxActions.globalModals.showBackfillFailureModal({
|
||||||
|
kind: BackfillFailureKind.NotFound,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw missingCaseError(response.error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: no awaits until we finish modifying attachments
|
||||||
|
|
||||||
// Since we are matching remote attachments with local attachments we need
|
// Since we are matching remote attachments with local attachments we need
|
||||||
// to make sure things are normalized before starting.
|
// to make sure things are normalized before starting.
|
||||||
message.set(
|
message.set(
|
||||||
@@ -463,3 +468,24 @@ export function isPermanentlyUndownloadable(
|
|||||||
// at this time.
|
// at this time.
|
||||||
return !AttachmentBackfill.isEnabledForJob(disposition, message);
|
return !AttachmentBackfill.isEnabledForJob(disposition, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPermanentlyUndownloadableWithoutBackfill(
|
||||||
|
attachment: AttachmentType
|
||||||
|
): boolean {
|
||||||
|
// Attachment is downloadable or user have not failed to download it yet
|
||||||
|
if (isDownloadable(attachment) || !attachment.error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Too big attachments cannot be retried anymore
|
||||||
|
if (attachment.wasTooBig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous backfill failed
|
||||||
|
if (attachment.backfillError) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@@ -7,16 +7,16 @@ import { noop } from 'lodash';
|
|||||||
|
|
||||||
import { DataWriter } from '../../sql/Client';
|
import { DataWriter } from '../../sql/Client';
|
||||||
import { IMAGE_PNG } from '../../types/MIME';
|
import { IMAGE_PNG } from '../../types/MIME';
|
||||||
import {
|
import { downloadAttachment } from '../../util/downloadAttachment';
|
||||||
AttachmentPermanentlyUndownloadableError,
|
|
||||||
downloadAttachment,
|
|
||||||
} from '../../util/downloadAttachment';
|
|
||||||
import { MediaTier } from '../../types/AttachmentDownload';
|
import { MediaTier } from '../../types/AttachmentDownload';
|
||||||
import { HTTPError } from '../../textsecure/Errors';
|
import { HTTPError } from '../../textsecure/Errors';
|
||||||
import { getCdnNumberForBackupTier } from '../../textsecure/downloadAttachment';
|
import { getCdnNumberForBackupTier } from '../../textsecure/downloadAttachment';
|
||||||
import { MASTER_KEY, MEDIA_ROOT_KEY } from '../backup/helpers';
|
import { MASTER_KEY, MEDIA_ROOT_KEY } from '../backup/helpers';
|
||||||
import { getMediaIdFromMediaName } from '../../services/backups/util/mediaId';
|
import { getMediaIdFromMediaName } from '../../services/backups/util/mediaId';
|
||||||
import { AttachmentVariant } from '../../types/Attachment';
|
import {
|
||||||
|
AttachmentVariant,
|
||||||
|
AttachmentPermanentlyUndownloadableError,
|
||||||
|
} from '../../types/Attachment';
|
||||||
|
|
||||||
describe('utils/downloadAttachment', () => {
|
describe('utils/downloadAttachment', () => {
|
||||||
const baseAttachment = {
|
const baseAttachment = {
|
||||||
|
@@ -3825,6 +3825,16 @@ export default class MessageReceiver
|
|||||||
logId
|
logId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
strictAssert(
|
||||||
|
targetMessage != null,
|
||||||
|
'MessageReceiver.handleAttachmentBackfillResponse: invalid target message'
|
||||||
|
);
|
||||||
|
strictAssert(
|
||||||
|
targetConversation != null,
|
||||||
|
'MessageReceiver.handleAttachmentBackfillResponse: ' +
|
||||||
|
'invalid target conversation'
|
||||||
|
);
|
||||||
|
|
||||||
let eventData: AttachmentBackfillResponseSyncEventData;
|
let eventData: AttachmentBackfillResponseSyncEventData;
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
eventData = {
|
eventData = {
|
||||||
@@ -3833,16 +3843,6 @@ export default class MessageReceiver
|
|||||||
targetConversation,
|
targetConversation,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
strictAssert(
|
|
||||||
targetMessage != null,
|
|
||||||
'MessageReceiver.handleAttachmentBackfillResponse: invalid target message'
|
|
||||||
);
|
|
||||||
strictAssert(
|
|
||||||
targetConversation != null,
|
|
||||||
'MessageReceiver.handleAttachmentBackfillResponse: ' +
|
|
||||||
'invalid target conversation'
|
|
||||||
);
|
|
||||||
|
|
||||||
const { attachments } = response;
|
const { attachments } = response;
|
||||||
strictAssert(
|
strictAssert(
|
||||||
attachments != null,
|
attachments != null,
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
mightBeOnBackupTier,
|
mightBeOnBackupTier,
|
||||||
type AttachmentType,
|
type AttachmentType,
|
||||||
AttachmentVariant,
|
AttachmentVariant,
|
||||||
|
AttachmentPermanentlyUndownloadableError,
|
||||||
} from '../types/Attachment';
|
} from '../types/Attachment';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import {
|
import {
|
||||||
@@ -115,9 +116,13 @@ export async function downloadAttachment(
|
|||||||
|
|
||||||
const { digest, incrementalMac, chunkSize, key, size } = attachment;
|
const { digest, incrementalMac, chunkSize, key, size } = attachment;
|
||||||
|
|
||||||
strictAssert(digest, `${logId}: missing digest`);
|
try {
|
||||||
strictAssert(key, `${logId}: missing key`);
|
strictAssert(digest, `${logId}: missing digest`);
|
||||||
strictAssert(isNumber(size), `${logId}: missing size`);
|
strictAssert(key, `${logId}: missing key`);
|
||||||
|
strictAssert(isNumber(size), `${logId}: missing size`);
|
||||||
|
} catch (error) {
|
||||||
|
throw new AttachmentPermanentlyUndownloadableError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
const mediaTier =
|
const mediaTier =
|
||||||
options?.mediaTier ??
|
options?.mediaTier ??
|
||||||
|
@@ -595,17 +595,18 @@ export type AttachmentBackfillAttachmentType = Readonly<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export type AttachmentBackfillResponseSyncEventData = Readonly<
|
export type AttachmentBackfillResponseSyncEventData = Readonly<
|
||||||
| {
|
{
|
||||||
error: Proto.SyncMessage.AttachmentBackfillResponse.Error;
|
targetMessage: AddressableMessage;
|
||||||
targetMessage?: AddressableMessage;
|
targetConversation: ConversationIdentifier;
|
||||||
targetConversation?: ConversationIdentifier;
|
} & (
|
||||||
}
|
| {
|
||||||
| {
|
error: Proto.SyncMessage.AttachmentBackfillResponse.Error;
|
||||||
attachments: ReadonlyArray<AttachmentBackfillAttachmentType>;
|
}
|
||||||
longText: AttachmentBackfillAttachmentType | undefined;
|
| {
|
||||||
targetMessage: AddressableMessage;
|
attachments: ReadonlyArray<AttachmentBackfillAttachmentType>;
|
||||||
targetConversation: ConversationIdentifier;
|
longText: AttachmentBackfillAttachmentType | undefined;
|
||||||
}
|
}
|
||||||
|
)
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export class AttachmentBackfillResponseSyncEvent extends ConfirmableEvent {
|
export class AttachmentBackfillResponseSyncEvent extends ConfirmableEvent {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
// Copyright 2018 Signal Messenger, LLC
|
// Copyright 2018 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +47,14 @@ const MIN_HEIGHT = 50;
|
|||||||
|
|
||||||
export class AttachmentSizeError extends Error {}
|
export class AttachmentSizeError extends Error {}
|
||||||
|
|
||||||
|
// Used for downlaods
|
||||||
|
|
||||||
|
export class AttachmentPermanentlyUndownloadableError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(`AttachmentPermanentlyUndownloadableError: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ScreenshotType = Omit<AttachmentType, 'size'> & {
|
type ScreenshotType = Omit<AttachmentType, 'size'> & {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
@@ -5,6 +5,7 @@ import {
|
|||||||
type AttachmentType,
|
type AttachmentType,
|
||||||
mightBeOnBackupTier,
|
mightBeOnBackupTier,
|
||||||
AttachmentVariant,
|
AttachmentVariant,
|
||||||
|
AttachmentPermanentlyUndownloadableError,
|
||||||
getAttachmentIdForLogging,
|
getAttachmentIdForLogging,
|
||||||
} from '../types/Attachment';
|
} from '../types/Attachment';
|
||||||
import { downloadAttachment as doDownloadAttachment } from '../textsecure/downloadAttachment';
|
import { downloadAttachment as doDownloadAttachment } from '../textsecure/downloadAttachment';
|
||||||
@@ -14,8 +15,6 @@ import { HTTPError } from '../textsecure/Errors';
|
|||||||
import { toLogFormat } from '../types/errors';
|
import { toLogFormat } from '../types/errors';
|
||||||
import type { ReencryptedAttachmentV2 } from '../AttachmentCrypto';
|
import type { ReencryptedAttachmentV2 } from '../AttachmentCrypto';
|
||||||
|
|
||||||
export class AttachmentPermanentlyUndownloadableError extends Error {}
|
|
||||||
|
|
||||||
export async function downloadAttachment({
|
export async function downloadAttachment({
|
||||||
attachment,
|
attachment,
|
||||||
options: { variant = AttachmentVariant.Default, onSizeUpdate, abortSignal },
|
options: { variant = AttachmentVariant.Default, onSizeUpdate, abortSignal },
|
||||||
@@ -105,7 +104,7 @@ export async function downloadAttachment({
|
|||||||
error instanceof HTTPError &&
|
error instanceof HTTPError &&
|
||||||
(error.code === 404 || error.code === 403)
|
(error.code === 404 || error.code === 403)
|
||||||
) {
|
) {
|
||||||
throw new AttachmentPermanentlyUndownloadableError();
|
throw new AttachmentPermanentlyUndownloadableError(`HTTP ${error.code}`);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user