Recover from CDN 416 Range error on attachment download.

This commit is contained in:
Alan Evans
2021-01-13 00:04:50 -04:00
committed by Greyson Parrelli
parent be91f2396c
commit adea15df10
3 changed files with 43 additions and 18 deletions

View File

@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.jobmanager.JobLogger;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraint;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.Hex;
@@ -33,13 +34,14 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemo
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.push.exceptions.RangeException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class AttachmentDownloadJob extends BaseJob { public final class AttachmentDownloadJob extends BaseJob {
public static final String KEY = "AttachmentDownloadJob"; public static final String KEY = "AttachmentDownloadJob";
@@ -107,12 +109,12 @@ public class AttachmentDownloadJob extends BaseJob {
} }
@Override @Override
public void onRun() throws IOException { public void onRun() throws Exception {
doWork(); doWork();
ApplicationDependencies.getMessageNotifier().updateNotification(context, 0); ApplicationDependencies.getMessageNotifier().updateNotification(context, 0);
} }
public void doWork() throws IOException { public void doWork() throws IOException, RetryLaterException {
Log.i(TAG, "onRun() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual); Log.i(TAG, "onRun() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual);
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
@@ -151,13 +153,14 @@ public class AttachmentDownloadJob extends BaseJob {
@Override @Override
protected boolean onShouldRetry(@NonNull Exception exception) { protected boolean onShouldRetry(@NonNull Exception exception) {
return (exception instanceof PushNetworkException); return exception instanceof PushNetworkException ||
exception instanceof RetryLaterException;
} }
private void retrieveAttachment(long messageId, private void retrieveAttachment(long messageId,
final AttachmentId attachmentId, final AttachmentId attachmentId,
final Attachment attachment) final Attachment attachment)
throws IOException throws IOException, RetryLaterException
{ {
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
@@ -169,6 +172,14 @@ public class AttachmentDownloadJob extends BaseJob {
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))); InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)));
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream); database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
} catch (RangeException e) {
Log.w(TAG, "Range exception, file size " + attachmentFile.length(), e);
if (attachmentFile.delete()) {
Log.i(TAG, "Deleted temp download file to recover");
throw new RetryLaterException(e);
} else {
throw new IOException("Failed to delete temp download file following range exception");
}
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException | MissingConfigurationException e) { } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException | MissingConfigurationException e) {
Log.w(TAG, "Experienced exception while trying to download an attachment.", e); Log.w(TAG, "Experienced exception while trying to download an attachment.", e);
markFailed(messageId, attachmentId); markFailed(messageId, attachmentId);

View File

@@ -0,0 +1,14 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
* <p>
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api.push.exceptions;
public final class RangeException extends NonSuccessfulResponseCodeException {
public RangeException(long requested) {
super("Range request out of bounds " + requested);
}
}

View File

@@ -59,6 +59,7 @@ import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.push.exceptions.RangeException;
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
import org.whispersystems.signalservice.api.push.exceptions.RemoteAttestationResponseExpiredException; import org.whispersystems.signalservice.api.push.exceptions.RemoteAttestationResponseExpiredException;
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException; import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
@@ -571,7 +572,8 @@ public class PushServiceSocket {
} }
public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener) public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
throws NonSuccessfulResponseCodeException, PushNetworkException, MissingConfigurationException { throws IOException, MissingConfigurationException
{
final String path; final String path;
if (cdnPath.getV2().isPresent()) { if (cdnPath.getV2().isPresent()) {
path = String.format(Locale.US, ATTACHMENT_ID_DOWNLOAD_PATH, cdnPath.getV2().get()); path = String.format(Locale.US, ATTACHMENT_ID_DOWNLOAD_PATH, cdnPath.getV2().get());
@@ -581,12 +583,6 @@ public class PushServiceSocket {
downloadFromCdn(destination, cdnNumber, path, maxSizeBytes, listener); downloadFromCdn(destination, cdnNumber, path, maxSizeBytes, listener);
} }
public void retrieveSticker(File destination, byte[] packId, int stickerId)
throws NonSuccessfulResponseCodeException, PushNetworkException, MissingConfigurationException {
String hexPackId = Hex.toStringCondensed(packId);
downloadFromCdn(destination, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null);
}
public byte[] retrieveSticker(byte[] packId, int stickerId) public byte[] retrieveSticker(byte[] packId, int stickerId)
throws NonSuccessfulResponseCodeException, PushNetworkException { throws NonSuccessfulResponseCodeException, PushNetworkException {
String hexPackId = Hex.toStringCondensed(packId); String hexPackId = Hex.toStringCondensed(packId);
@@ -694,7 +690,8 @@ public class PushServiceSocket {
} }
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes) public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
throws NonSuccessfulResponseCodeException, PushNetworkException { throws IOException
{
try { try {
downloadFromCdn(destination, 0, path, maxSizeBytes, null); downloadFromCdn(destination, 0, path, maxSizeBytes, null);
} catch (MissingConfigurationException e) { } catch (MissingConfigurationException e) {
@@ -971,11 +968,10 @@ public class PushServiceSocket {
} }
private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener) private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener)
throws PushNetworkException, NonSuccessfulResponseCodeException, MissingConfigurationException { throws IOException, MissingConfigurationException
{
try (FileOutputStream outputStream = new FileOutputStream(destination, true)) { try (FileOutputStream outputStream = new FileOutputStream(destination, true)) {
downloadFromCdn(outputStream, destination.length(), cdnNumber, path, maxSizeBytes, listener); downloadFromCdn(outputStream, destination.length(), cdnNumber, path, maxSizeBytes, listener);
} catch (IOException e) {
throw new PushNetworkException(e);
} }
} }
@@ -1037,13 +1033,17 @@ public class PushServiceSocket {
} }
return; return;
} else if (response.code() == 416) {
throw new RangeException(offset);
} }
} catch (NonSuccessfulResponseCodeException | PushNetworkException e) {
throw e;
} catch (IOException e) { } catch (IOException e) {
throw new PushNetworkException(e);
} finally {
if (body != null) { if (body != null) {
body.close(); body.close();
} }
throw new PushNetworkException(e);
} finally {
synchronized (connections) { synchronized (connections) {
connections.remove(call); connections.remove(call);
} }