Optimize uploads during media composition.

By uploading in advance (when on unmetered connections), media messages
can send almost instantly.
This commit is contained in:
Greyson Parrelli
2020-01-08 15:56:51 -05:00
parent 92e97e61c1
commit fadcc606f8
37 changed files with 1413 additions and 452 deletions

View File

@@ -341,7 +341,8 @@ public class SignalServiceMessageSender {
dataStream,
ciphertextLength,
new AttachmentCipherOutputStreamFactory(attachmentKey),
attachment.getListener());
attachment.getListener(),
attachment.getCancelationSignal());
AttachmentUploadAttributes uploadAttributes = null;

View File

@@ -7,6 +7,7 @@
package org.whispersystems.signalservice.api.messages;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
import java.io.InputStream;
@@ -39,16 +40,17 @@ public abstract class SignalServiceAttachment {
public static class Builder {
private InputStream inputStream;
private String contentType;
private String fileName;
private long length;
private ProgressListener listener;
private boolean voiceNote;
private int width;
private int height;
private String caption;
private String blurHash;
private InputStream inputStream;
private String contentType;
private String fileName;
private long length;
private ProgressListener listener;
private CancelationSignal cancelationSignal;
private boolean voiceNote;
private int width;
private int height;
private String caption;
private String blurHash;
private Builder() {}
@@ -77,6 +79,11 @@ public abstract class SignalServiceAttachment {
return this;
}
public Builder withCancelationSignal(CancelationSignal cancelationSignal) {
this.cancelationSignal = cancelationSignal;
return this;
}
public Builder withVoiceNote(boolean voiceNote) {
this.voiceNote = voiceNote;
return this;
@@ -117,7 +124,8 @@ public abstract class SignalServiceAttachment {
height,
Optional.fromNullable(caption),
Optional.fromNullable(blurHash),
listener);
listener,
cancelationSignal);
}
}

View File

@@ -7,6 +7,7 @@
package org.whispersystems.signalservice.api.messages;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
import java.io.InputStream;
@@ -15,33 +16,47 @@ import java.io.InputStream;
*/
public class SignalServiceAttachmentStream extends SignalServiceAttachment {
private final InputStream inputStream;
private final long length;
private final Optional<String> fileName;
private final ProgressListener listener;
private final Optional<byte[]> preview;
private final boolean voiceNote;
private final int width;
private final int height;
private final Optional<String> caption;
private final Optional<String> blurHash;
private final InputStream inputStream;
private final long length;
private final Optional<String> fileName;
private final ProgressListener listener;
private final CancelationSignal cancelationSignal;
private final Optional<byte[]> preview;
private final boolean voiceNote;
private final int width;
private final int height;
private final Optional<String> caption;
private final Optional<String> blurHash;
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, ProgressListener listener) {
this(inputStream, contentType, length, fileName, voiceNote, Optional.<byte[]>absent(), 0, 0, Optional.<String>absent(), Optional.<String>absent(), listener);
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, ProgressListener listener, CancelationSignal cancelationSignal) {
this(inputStream, contentType, length, fileName, voiceNote, Optional.<byte[]>absent(), 0, 0, Optional.<String>absent(), Optional.<String>absent(), listener, cancelationSignal);
}
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, Optional<byte[]> preview, int width, int height, Optional<String> caption, Optional<String> blurHash, ProgressListener listener) {
public SignalServiceAttachmentStream(InputStream inputStream,
String contentType,
long length,
Optional<String> fileName,
boolean voiceNote,
Optional<byte[]> preview,
int width,
int height,
Optional<String> caption,
Optional<String> blurHash,
ProgressListener listener,
CancelationSignal cancelationSignal)
{
super(contentType);
this.inputStream = inputStream;
this.length = length;
this.fileName = fileName;
this.listener = listener;
this.voiceNote = voiceNote;
this.preview = preview;
this.width = width;
this.height = height;
this.caption = caption;
this.blurHash = blurHash;
this.inputStream = inputStream;
this.length = length;
this.fileName = fileName;
this.listener = listener;
this.voiceNote = voiceNote;
this.preview = preview;
this.width = width;
this.height = height;
this.caption = caption;
this.blurHash = blurHash;
this.cancelationSignal = cancelationSignal;
}
@Override
@@ -70,6 +85,10 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment {
return listener;
}
public CancelationSignal getCancelationSignal() {
return cancelationSignal;
}
public Optional<byte[]> getPreview() {
return preview;
}

View File

@@ -55,7 +55,7 @@ public class DeviceContactsInputStream extends ChunkedInputStream {
InputStream avatarStream = new LimitedInputStream(in, avatarLength);
String avatarContentType = details.getAvatar().getContentType();
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null));
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null, null));
}
if (details.hasVerified()) {

View File

@@ -52,7 +52,7 @@ public class DeviceGroupsInputStream extends ChunkedInputStream{
InputStream avatarStream = new ChunkedInputStream.LimitedInputStream(in, avatarLength);
String avatarContentType = details.getAvatar().getContentType();
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null));
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null, null));
}
if (details.hasExpireTimer() && details.getExpireTimer() > 0) {

View File

@@ -7,6 +7,7 @@
package org.whispersystems.signalservice.internal.push;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
import org.whispersystems.signalservice.internal.push.http.OutputStreamFactory;
import java.io.InputStream;
@@ -18,15 +19,18 @@ public class PushAttachmentData {
private final long dataSize;
private final OutputStreamFactory outputStreamFactory;
private final ProgressListener listener;
private final CancelationSignal cancelationSignal;
public PushAttachmentData(String contentType, InputStream data, long dataSize,
OutputStreamFactory outputStreamFactory, ProgressListener listener)
OutputStreamFactory outputStreamFactory, ProgressListener listener,
CancelationSignal cancelationSignal)
{
this.contentType = contentType;
this.data = data;
this.dataSize = dataSize;
this.outputStreamFactory = outputStreamFactory;
this.listener = listener;
this.cancelationSignal = cancelationSignal;
}
public String getContentType() {
@@ -48,4 +52,8 @@ public class PushAttachmentData {
public ProgressListener getListener() {
return listener;
}
public CancelationSignal getCancelationSignal() {
return cancelationSignal;
}
}

View File

@@ -50,6 +50,7 @@ import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResp
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException;
import org.whispersystems.signalservice.internal.push.exceptions.StaleDevicesException;
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
import org.whispersystems.signalservice.internal.push.http.DigestingRequestBody;
import org.whispersystems.signalservice.internal.push.http.OutputStreamFactory;
import org.whispersystems.signalservice.internal.storage.protos.ReadOperation;
@@ -577,7 +578,7 @@ public class PushServiceSocket {
formAttributes.getCredential(), formAttributes.getDate(),
formAttributes.getSignature(), profileAvatar.getData(),
profileAvatar.getContentType(), profileAvatar.getDataLength(),
profileAvatar.getOutputStreamFactory(), null);
profileAvatar.getOutputStreamFactory(), null, null);
}
}
@@ -763,7 +764,8 @@ public class PushServiceSocket {
uploadAttributes.getCredential(), uploadAttributes.getDate(),
uploadAttributes.getSignature(), attachment.getData(),
"application/octet-stream", attachment.getDataSize(),
attachment.getOutputStreamFactory(), attachment.getListener());
attachment.getOutputStreamFactory(), attachment.getListener(),
attachment.getCancelationSignal());
return new Pair<>(id, digest);
}
@@ -851,7 +853,8 @@ public class PushServiceSocket {
private byte[] uploadToCdn(String path, String acl, String key, String policy, String algorithm,
String credential, String date, String signature,
InputStream data, String contentType, long length,
OutputStreamFactory outputStreamFactory, ProgressListener progressListener)
OutputStreamFactory outputStreamFactory, ProgressListener progressListener,
CancelationSignal cancelationSignal)
throws PushNetworkException, NonSuccessfulResponseCodeException
{
ConnectionHolder connectionHolder = getRandom(cdnClients, random);
@@ -861,7 +864,7 @@ public class PushServiceSocket {
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener, cancelationSignal);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)

View File

@@ -0,0 +1,8 @@
package org.whispersystems.signalservice.internal.push.http;
/**
* Used to communicate to observers whether or not something is canceled.
*/
public interface CancelationSignal {
boolean isCanceled();
}

View File

@@ -18,19 +18,22 @@ public class DigestingRequestBody extends RequestBody {
private final String contentType;
private final long contentLength;
private final ProgressListener progressListener;
private final CancelationSignal cancelationSignal;
private byte[] digest;
public DigestingRequestBody(InputStream inputStream,
OutputStreamFactory outputStreamFactory,
String contentType, long contentLength,
ProgressListener progressListener)
ProgressListener progressListener,
CancelationSignal cancelationSignal)
{
this.inputStream = inputStream;
this.outputStreamFactory = outputStreamFactory;
this.contentType = contentType;
this.contentLength = contentLength;
this.progressListener = progressListener;
this.cancelationSignal = cancelationSignal;
}
@Override
@@ -47,6 +50,10 @@ public class DigestingRequestBody extends RequestBody {
long total = 0;
while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new IOException("Canceled!");
}
outputStream.write(buffer, 0, read);
total += read;