session-android/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java

255 lines
10 KiB
Java
Raw Normal View History

package org.thoughtcrime.securesms.jobs;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
2019-03-28 08:56:35 -07:00
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
2018-08-01 11:09:24 -04:00
import org.thoughtcrime.securesms.logging.Log;
2017-02-17 20:27:11 -08:00
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
2019-03-28 08:56:35 -07:00
public class AttachmentDownloadJob extends BaseJob implements InjectableType {
public static final String KEY = "AttachmentDownloadJob";
2017-03-09 15:04:16 -08:00
private static final int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
private static final String TAG = AttachmentDownloadJob.class.getSimpleName();
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_PART_ROW_ID = "part_row_id";
private static final String KEY_PAR_UNIQUE_ID = "part_unique_id";
private static final String KEY_MANUAL = "part_manual";
2019-03-28 08:56:35 -07:00
@Inject SignalServiceMessageReceiver messageReceiver;
private long messageId;
private long partRowId;
private long partUniqueId;
private boolean manual;
2019-03-28 08:56:35 -07:00
public AttachmentDownloadJob(long messageId, AttachmentId attachmentId, boolean manual) {
this(new Job.Parameters.Builder()
.setQueue("AttachmentDownloadJob" + attachmentId.getRowId() + "-" + attachmentId.getUniqueId())
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(25)
.build(),
messageId,
attachmentId,
manual);
}
2019-03-28 08:56:35 -07:00
private AttachmentDownloadJob(@NonNull Job.Parameters parameters, long messageId, AttachmentId attachmentId, boolean manual) {
super(parameters);
this.messageId = messageId;
this.partRowId = attachmentId.getRowId();
this.partUniqueId = attachmentId.getUniqueId();
this.manual = manual;
}
@Override
2019-03-28 08:56:35 -07:00
public @NonNull Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putLong(KEY_PART_ROW_ID, partRowId)
.putLong(KEY_PAR_UNIQUE_ID, partUniqueId)
.putBoolean(KEY_MANUAL, manual)
.build();
}
@Override
2019-03-28 08:56:35 -07:00
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
Log.i(TAG, "onAdded() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual);
2018-11-08 23:33:37 -08:00
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final DatabaseAttachment attachment = database.getAttachment(attachmentId);
final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE;
if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) {
Log.i(TAG, "onAdded() Marking attachment progress as 'started'");
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
}
}
@Override
public void onRun() throws IOException {
Log.i(TAG, "onRun() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual);
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final DatabaseAttachment attachment = database.getAttachment(attachmentId);
if (attachment == null) {
Log.w(TAG, "attachment no longer exists.");
return;
}
if (!attachment.isInProgress()) {
Log.w(TAG, "Attachment was already downloaded.");
return;
}
if (!manual && !AttachmentUtil.isAutoDownloadPermitted(context, attachment)) {
Log.w(TAG, "Attachment can't be auto downloaded...");
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_PENDING);
return;
}
2018-08-02 09:25:33 -04:00
Log.i(TAG, "Downloading push part " + attachmentId);
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
retrieveAttachment(messageId, attachmentId, attachment);
MessageNotifier.updateNotification(context);
}
@Override
public void onCanceled() {
Log.w(TAG, "onCanceled() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
markFailed(messageId, attachmentId);
}
@Override
protected boolean onShouldRetry(@NonNull Exception exception) {
2014-12-12 01:03:24 -08:00
return (exception instanceof PushNetworkException);
}
private void retrieveAttachment(long messageId,
final AttachmentId attachmentId,
final Attachment attachment)
throws IOException
{
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
File attachmentFile = null;
try {
attachmentFile = createTempFile();
SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment);
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)));
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {
Log.w(TAG, "Experienced exception while trying to download an attachment.", e);
markFailed(messageId, attachmentId);
} finally {
if (attachmentFile != null) {
//noinspection ResultOfMethodCallIgnored
attachmentFile.delete();
}
}
}
@VisibleForTesting
SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment)
throws InvalidPartException
{
if (TextUtils.isEmpty(attachment.getLocation())) {
throw new InvalidPartException("empty content id");
}
if (TextUtils.isEmpty(attachment.getKey())) {
throw new InvalidPartException("empty encrypted key");
}
try {
long id = Long.parseLong(attachment.getLocation());
byte[] key = Base64.decode(attachment.getKey());
String relay = null;
if (TextUtils.isEmpty(attachment.getRelay())) {
relay = attachment.getRelay();
}
if (attachment.getDigest() != null) {
2018-08-02 09:25:33 -04:00
Log.i(TAG, "Downloading attachment with digest: " + Hex.toString(attachment.getDigest()));
} else {
2018-08-02 09:25:33 -04:00
Log.i(TAG, "Downloading attachment with no digest...");
}
2018-05-22 02:13:10 -07:00
return new SignalServiceAttachmentPointer(id, null, key,
Optional.of(Util.toIntExact(attachment.getSize())),
Optional.absent(),
2018-01-18 10:01:41 -08:00
0, 0,
Optional.fromNullable(attachment.getDigest()),
Optional.fromNullable(attachment.getFileName()),
attachment.isVoiceNote(),
Optional.absent());
} catch (IOException | ArithmeticException e) {
Log.w(TAG, e);
throw new InvalidPartException(e);
}
}
private File createTempFile() throws InvalidPartException {
try {
File file = File.createTempFile("push-attachment", "tmp", context.getCacheDir());
file.deleteOnExit();
return file;
} catch (IOException e) {
throw new InvalidPartException(e);
}
}
private void markFailed(long messageId, AttachmentId attachmentId) {
try {
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
database.setTransferProgressFailed(attachmentId, messageId);
} catch (MmsException e) {
Log.w(TAG, e);
}
}
@VisibleForTesting static class InvalidPartException extends Exception {
InvalidPartException(String s) {super(s);}
InvalidPartException(Exception e) {super(e);}
}
2019-03-28 08:56:35 -07:00
public static final class Factory implements Job.Factory<AttachmentDownloadJob> {
@Override
public @NonNull AttachmentDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new AttachmentDownloadJob(parameters,
data.getLong(KEY_MESSAGE_ID),
new AttachmentId(data.getLong(KEY_PART_ROW_ID), data.getLong(KEY_PAR_UNIQUE_ID)),
data.getBoolean(KEY_MANUAL));
}
}
}