mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 09:17:44 +00:00
Request a small chunk instead of HEAD for images of unknown size.
This commit is contained in:
parent
29cdb5290b
commit
f3f6cc87d9
@ -9,11 +9,13 @@ import com.bumptech.glide.util.ContentLengthInputStream;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -41,23 +43,31 @@ public class ChunkedDataFetcher {
|
|||||||
|
|
||||||
public RequestController fetch(@NonNull String url, long contentLength, @NonNull Callback callback) {
|
public RequestController fetch(@NonNull String url, long contentLength, @NonNull Callback callback) {
|
||||||
if (contentLength <= 0) {
|
if (contentLength <= 0) {
|
||||||
return fetch(url, callback);
|
return fetchChunksWithUnknownTotalSize(url, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositeRequestController compositeController = new CompositeRequestController();
|
CompositeRequestController compositeController = new CompositeRequestController();
|
||||||
fetchChunks(url, contentLength, compositeController, callback);
|
fetchChunks(url, contentLength, Optional.absent(), compositeController, callback);
|
||||||
return compositeController;
|
return compositeController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestController fetch(@NonNull String url, @NonNull Callback callback) {
|
private RequestController fetchChunksWithUnknownTotalSize(@NonNull String url, @NonNull Callback callback) {
|
||||||
CompositeRequestController compositeController = new CompositeRequestController();
|
CompositeRequestController compositeController = new CompositeRequestController();
|
||||||
|
|
||||||
Call headCall = client.newCall(new Request.Builder().url(url).head().cacheControl(NO_CACHE).build());
|
long chunkSize = new SecureRandom().nextInt(1024) + 1024;
|
||||||
compositeController.addController(new CallRequestController(headCall));
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.cacheControl(NO_CACHE)
|
||||||
|
.addHeader("Range", "bytes=0-" + (chunkSize - 1))
|
||||||
|
.addHeader("Accept-Encoding", "identity")
|
||||||
|
.build();
|
||||||
|
|
||||||
headCall.enqueue(new okhttp3.Callback() {
|
Call firstChunkCall = client.newCall(request);
|
||||||
|
compositeController.addController(new CallRequestController(firstChunkCall));
|
||||||
|
|
||||||
|
firstChunkCall.enqueue(new okhttp3.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call call, IOException e) {
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
if (!compositeController.isCanceled()) {
|
if (!compositeController.isCanceled()) {
|
||||||
callback.onFailure(e);
|
callback.onFailure(e);
|
||||||
compositeController.cancel();
|
compositeController.cancel();
|
||||||
@ -65,9 +75,8 @@ public class ChunkedDataFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call call, Response response) throws IOException {
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
String contentLength = response.header("Content-Length");
|
String contentRange = response.header("Content-Range");
|
||||||
String acceptRanges = response.header("Accept-Ranges");
|
|
||||||
|
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
Log.w(TAG, "Non-successful response code: " + response.code());
|
Log.w(TAG, "Non-successful response code: " + response.code());
|
||||||
@ -77,39 +86,64 @@ public class ChunkedDataFetcher {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(contentLength)) {
|
if (TextUtils.isEmpty(contentRange)) {
|
||||||
Log.w(TAG, "Missing Content-Length header.");
|
Log.w(TAG, "Missing Content-Range header.");
|
||||||
callback.onFailure(new IOException("Missing Content-Length header."));
|
callback.onFailure(new IOException("Missing Content-Length header."));
|
||||||
compositeController.cancel();
|
compositeController.cancel();
|
||||||
if (response.body() != null) response.body().close();
|
if (response.body() != null) response.body().close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long parsedContentLength;
|
if (response.body() == null) {
|
||||||
try {
|
Log.w(TAG, "Missing body.");
|
||||||
parsedContentLength = Long.parseLong(contentLength);
|
callback.onFailure(new IOException("Missing body on initial request."));
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.w(TAG, "Invalid Content-Length value.");
|
|
||||||
callback.onFailure(new IOException("Invalid Content-Length value."));
|
|
||||||
compositeController.cancel();
|
compositeController.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.body() != null) {
|
Optional<Long> contentLength = parseLengthFromContentRange(contentRange);
|
||||||
response.body().close();
|
|
||||||
|
if (!contentLength.isPresent()) {
|
||||||
|
Log.w(TAG, "Unable to parse length from Content-Range.");
|
||||||
|
callback.onFailure(new IOException("Unable to get parse length from Content-Range."));
|
||||||
|
compositeController.cancel();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchChunks(url, parsedContentLength, compositeController, callback);
|
if (chunkSize >= contentLength.get()) {
|
||||||
|
try {
|
||||||
|
callback.onSuccess(response.body().byteStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
compositeController.cancel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
InputStream stream = ContentLengthInputStream.obtain(response.body().byteStream(), chunkSize);
|
||||||
|
fetchChunks(url, contentLength.get(), Optional.of(new Pair<>(stream, chunkSize)), compositeController, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return compositeController;
|
return compositeController;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchChunks(@NonNull String url, long contentLength, CompositeRequestController compositeController, Callback callback) {
|
private void fetchChunks(@NonNull String url,
|
||||||
|
long contentLength,
|
||||||
|
Optional<Pair<InputStream, Long>> firstChunk,
|
||||||
|
CompositeRequestController compositeController,
|
||||||
|
Callback callback)
|
||||||
|
{
|
||||||
List<ByteRange> requestPattern;
|
List<ByteRange> requestPattern;
|
||||||
try {
|
try {
|
||||||
requestPattern = getRequestPattern(contentLength);
|
if (firstChunk.isPresent()) {
|
||||||
|
requestPattern = Stream.of(getRequestPattern(contentLength - firstChunk.get().second()))
|
||||||
|
.map(b -> new ByteRange(b.start + firstChunk.get().second(),
|
||||||
|
b.end + firstChunk.get().second(),
|
||||||
|
b.ignoreFirst))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
requestPattern = getRequestPattern(contentLength);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
callback.onFailure(e);
|
callback.onFailure(e);
|
||||||
compositeController.cancel();
|
compositeController.cancel();
|
||||||
@ -118,7 +152,11 @@ public class ChunkedDataFetcher {
|
|||||||
|
|
||||||
SignalExecutors.IO.execute(() -> {
|
SignalExecutors.IO.execute(() -> {
|
||||||
List<CallRequestController> controllers = Stream.of(requestPattern).map(range -> makeChunkRequest(client, url, range)).toList();
|
List<CallRequestController> controllers = Stream.of(requestPattern).map(range -> makeChunkRequest(client, url, range)).toList();
|
||||||
List<InputStream> streams = new ArrayList<>(controllers.size());
|
List<InputStream> streams = new ArrayList<>(controllers.size() + (firstChunk.isPresent() ? 1 : 0));
|
||||||
|
|
||||||
|
if (firstChunk.isPresent()) {
|
||||||
|
streams.add(firstChunk.get().first());
|
||||||
|
}
|
||||||
|
|
||||||
Stream.of(controllers).forEach(compositeController::addController);
|
Stream.of(controllers).forEach(compositeController::addController);
|
||||||
|
|
||||||
@ -157,12 +195,12 @@ public class ChunkedDataFetcher {
|
|||||||
|
|
||||||
call.enqueue(new okhttp3.Callback() {
|
call.enqueue(new okhttp3.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call call, IOException e) {
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
callController.cancel();
|
callController.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call call, Response response) throws IOException {
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
callController.cancel();
|
callController.cancel();
|
||||||
if (response.body() != null) response.body().close();
|
if (response.body() != null) response.body().close();
|
||||||
@ -183,6 +221,22 @@ public class ChunkedDataFetcher {
|
|||||||
return callController;
|
return callController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Long> parseLengthFromContentRange(@NonNull String contentRange) {
|
||||||
|
int totalStartPos = contentRange.indexOf('/');
|
||||||
|
|
||||||
|
if (totalStartPos >= 0 && contentRange.length() > totalStartPos + 1) {
|
||||||
|
String totalString = contentRange.substring(totalStartPos + 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Optional.of(Long.parseLong(totalString));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
private List<ByteRange> getRequestPattern(long size) throws IOException {
|
private List<ByteRange> getRequestPattern(long size) throws IOException {
|
||||||
if (size > MB) return getRequestPattern(size, MB);
|
if (size > MB) return getRequestPattern(size, MB);
|
||||||
else if (size > 500 * KB) return getRequestPattern(size, 500 * KB);
|
else if (size > 500 * KB) return getRequestPattern(size, 500 * KB);
|
||||||
|
@ -14,6 +14,7 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -48,6 +49,10 @@ public class ContentProxySelector extends ProxySelector {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectFailed(URI uri, SocketAddress address, IOException failure) {
|
public void connectFailed(URI uri, SocketAddress address, IOException failure) {
|
||||||
Log.w(TAG, "Connection failed.", failure);
|
if (failure instanceof SocketException) {
|
||||||
|
Log.d(TAG, "Socket exception. Likely a cancellation.");
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Connection failed.", failure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user