diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index f242f55633..637881396f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.jobmanager.JobLogger; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraint; import org.thoughtcrime.securesms.mms.MmsException; +import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.Base64; 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.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; +import org.whispersystems.signalservice.api.push.exceptions.RangeException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; -public class AttachmentDownloadJob extends BaseJob { +public final class AttachmentDownloadJob extends BaseJob { public static final String KEY = "AttachmentDownloadJob"; @@ -107,12 +109,12 @@ public class AttachmentDownloadJob extends BaseJob { } @Override - public void onRun() throws IOException { + public void onRun() throws Exception { doWork(); 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); final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); @@ -151,13 +153,14 @@ public class AttachmentDownloadJob extends BaseJob { @Override protected boolean onShouldRetry(@NonNull Exception exception) { - return (exception instanceof PushNetworkException); + return exception instanceof PushNetworkException || + exception instanceof RetryLaterException; } private void retrieveAttachment(long messageId, final AttachmentId attachmentId, final Attachment attachment) - throws IOException + throws IOException, RetryLaterException { 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))); 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) { Log.w(TAG, "Experienced exception while trying to download an attachment.", e); markFailed(messageId, attachmentId); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/RangeException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/RangeException.java new file mode 100644 index 0000000000..b1cbc57f9b --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/RangeException.java @@ -0,0 +1,14 @@ +/** + * Copyright (C) 2014-2016 Open Whisper Systems + *
+ * 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); + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index a7def6c3fc..a700081a7b 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -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.NotFoundException; 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.RemoteAttestationResponseExpiredException; 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) - throws NonSuccessfulResponseCodeException, PushNetworkException, MissingConfigurationException { + throws IOException, MissingConfigurationException + { final String path; if (cdnPath.getV2().isPresent()) { 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); } - 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) throws NonSuccessfulResponseCodeException, PushNetworkException { String hexPackId = Hex.toStringCondensed(packId); @@ -694,7 +690,8 @@ public class PushServiceSocket { } public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes) - throws NonSuccessfulResponseCodeException, PushNetworkException { + throws IOException + { try { downloadFromCdn(destination, 0, path, maxSizeBytes, null); } catch (MissingConfigurationException e) { @@ -971,11 +968,10 @@ public class PushServiceSocket { } 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)) { downloadFromCdn(outputStream, destination.length(), cdnNumber, path, maxSizeBytes, listener); - } catch (IOException e) { - throw new PushNetworkException(e); } } @@ -1037,13 +1033,17 @@ public class PushServiceSocket { } return; + } else if (response.code() == 416) { + throw new RangeException(offset); } + } catch (NonSuccessfulResponseCodeException | PushNetworkException e) { + throw e; } catch (IOException e) { + throw new PushNetworkException(e); + } finally { if (body != null) { body.close(); } - throw new PushNetworkException(e); - } finally { synchronized (connections) { connections.remove(call); }