Implement DownloadClient using HttpURLConnection
The version of OkHttp used in AOSP doesn't handle dynamic table size updates [1] properly [2]. Instead of fixing OkHttp or importing a prebuilt updated version, implement a new download client only using HttpURLConnection, which seems to work properly. [1] https://tools.ietf.org/html/rfc7541#section-6.3 [2] https://trac.nginx.org/nginx/ticket/1397 Change-Id: I3eedf7326f2017812c4a12d41f9ea028d255f7a8
This commit is contained in:
@@ -12,8 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
|
|||||||
android-support-v7-appcompat \
|
android-support-v7-appcompat \
|
||||||
android-support-v7-cardview \
|
android-support-v7-cardview \
|
||||||
android-support-v7-preference \
|
android-support-v7-preference \
|
||||||
android-support-v7-recyclerview \
|
android-support-v7-recyclerview
|
||||||
okhttp
|
|
||||||
|
|
||||||
LOCAL_RESOURCE_DIR := \
|
LOCAL_RESOURCE_DIR := \
|
||||||
$(TOP)/frameworks/support/design/res \
|
$(TOP)/frameworks/support/design/res \
|
||||||
|
@@ -385,11 +385,18 @@ public class UpdatesActivity extends UpdatesListActivity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final DownloadClient downloadClient = new DownloadClient.Builder()
|
final DownloadClient downloadClient;
|
||||||
|
try {
|
||||||
|
downloadClient = new DownloadClient.Builder()
|
||||||
.setUrl(url)
|
.setUrl(url)
|
||||||
.setDestination(jsonFileTmp)
|
.setDestination(jsonFileTmp)
|
||||||
.setDownloadCallback(callback)
|
.setDownloadCallback(callback)
|
||||||
.build();
|
.build();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
Log.e(TAG, "Could not build download client");
|
||||||
|
showSnackbar(R.string.snack_updates_check_failed, Snackbar.LENGTH_LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -105,12 +105,17 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
DownloadClient downloadClient = new DownloadClient.Builder()
|
||||||
.setUrl(url)
|
.setUrl(url)
|
||||||
.setDestination(jsonNew)
|
.setDestination(jsonNew)
|
||||||
.setDownloadCallback(callback)
|
.setDownloadCallback(callback)
|
||||||
.build();
|
.build();
|
||||||
downloadClient.start();
|
downloadClient.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Could not fetch list, scheduling new check", e);
|
||||||
|
scheduleUpdatesCheck(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showNotification(Context context) {
|
private static void showNotification(Context context) {
|
||||||
|
@@ -31,6 +31,7 @@ import org.lineageos.updater.model.UpdateInfo;
|
|||||||
import org.lineageos.updater.model.UpdateStatus;
|
import org.lineageos.updater.model.UpdateStatus;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -365,12 +366,20 @@ public class UpdaterController implements Controller {
|
|||||||
Log.d(TAG, "Changing name with " + destination.getName());
|
Log.d(TAG, "Changing name with " + destination.getName());
|
||||||
}
|
}
|
||||||
update.setFile(destination);
|
update.setFile(destination);
|
||||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
DownloadClient downloadClient;
|
||||||
|
try {
|
||||||
|
downloadClient = new DownloadClient.Builder()
|
||||||
.setUrl(update.getDownloadUrl())
|
.setUrl(update.getDownloadUrl())
|
||||||
.setDestination(update.getFile())
|
.setDestination(update.getFile())
|
||||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||||
.setProgressListener(getProgressListener(downloadId))
|
.setProgressListener(getProgressListener(downloadId))
|
||||||
.build();
|
.build();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
Log.e(TAG, "Could not build download client");
|
||||||
|
update.setStatus(UpdateStatus.PAUSED_ERROR);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
||||||
update.setStatus(UpdateStatus.STARTING);
|
update.setStatus(UpdateStatus.STARTING);
|
||||||
notifyUpdateChange(downloadId);
|
notifyUpdateChange(downloadId);
|
||||||
@@ -399,12 +408,20 @@ public class UpdaterController implements Controller {
|
|||||||
verifyUpdateAsync(downloadId);
|
verifyUpdateAsync(downloadId);
|
||||||
notifyUpdateChange(downloadId);
|
notifyUpdateChange(downloadId);
|
||||||
} else {
|
} else {
|
||||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
DownloadClient downloadClient;
|
||||||
|
try {
|
||||||
|
downloadClient = new DownloadClient.Builder()
|
||||||
.setUrl(update.getDownloadUrl())
|
.setUrl(update.getDownloadUrl())
|
||||||
.setDestination(update.getFile())
|
.setDestination(update.getFile())
|
||||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||||
.setProgressListener(getProgressListener(downloadId))
|
.setProgressListener(getProgressListener(downloadId))
|
||||||
.build();
|
.build();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
Log.e(TAG, "Could not build download client");
|
||||||
|
update.setStatus(UpdateStatus.PAUSED_ERROR);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
||||||
update.setStatus(UpdateStatus.STARTING);
|
update.setStatus(UpdateStatus.STARTING);
|
||||||
notifyUpdateChange(downloadId);
|
notifyUpdateChange(downloadId);
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
package org.lineageos.updater.download;
|
package org.lineageos.updater.download;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ public interface DownloadClient {
|
|||||||
private DownloadClient.DownloadCallback mCallback;
|
private DownloadClient.DownloadCallback mCallback;
|
||||||
private DownloadClient.ProgressListener mProgressListener;
|
private DownloadClient.ProgressListener mProgressListener;
|
||||||
|
|
||||||
public DownloadClient build() {
|
public DownloadClient build() throws IOException {
|
||||||
if (mUrl == null) {
|
if (mUrl == null) {
|
||||||
throw new IllegalStateException("No download URL defined");
|
throw new IllegalStateException("No download URL defined");
|
||||||
} else if (mDestination == null) {
|
} else if (mDestination == null) {
|
||||||
@@ -71,7 +72,7 @@ public interface DownloadClient {
|
|||||||
} else if (mCallback == null) {
|
} else if (mCallback == null) {
|
||||||
throw new IllegalStateException("No download callback defined");
|
throw new IllegalStateException("No download callback defined");
|
||||||
}
|
}
|
||||||
return new OkHttpDownloadClient(mUrl, mDestination, mProgressListener, mCallback);
|
return new HttpURLConnectionClient(mUrl, mDestination, mProgressListener, mCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setUrl(String url) {
|
public Builder setUrl(String url) {
|
||||||
|
211
src/org/lineageos/updater/download/HttpURLConnectionClient.java
Normal file
211
src/org/lineageos/updater/download/HttpURLConnectionClient.java
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater.download;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HttpURLConnectionClient implements DownloadClient {
|
||||||
|
|
||||||
|
private final static String TAG = "HttpURLConnectionClient";
|
||||||
|
|
||||||
|
private final HttpURLConnection mClient;
|
||||||
|
|
||||||
|
private final File mDestination;
|
||||||
|
private final DownloadClient.ProgressListener mProgressListener;
|
||||||
|
private final DownloadClient.DownloadCallback mCallback;
|
||||||
|
|
||||||
|
private DownloadThread mDownloadThread;
|
||||||
|
|
||||||
|
public class Headers implements DownloadClient.Headers {
|
||||||
|
@Override
|
||||||
|
public String get(String name) {
|
||||||
|
return mClient.getHeaderField(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAll() {
|
||||||
|
return mClient.getHeaderFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnectionClient(String url, File destination,
|
||||||
|
DownloadClient.ProgressListener progressListener,
|
||||||
|
DownloadClient.DownloadCallback callback) throws IOException {
|
||||||
|
mClient = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
mDestination = destination;
|
||||||
|
mProgressListener = progressListener;
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (mDownloadThread != null) {
|
||||||
|
Log.e(TAG, "Already downloading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFileInternalCommon(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resume() {
|
||||||
|
if (mDownloadThread != null) {
|
||||||
|
Log.e(TAG, "Already downloading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFileResumeInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (mDownloadThread == null) {
|
||||||
|
Log.e(TAG, "Not downloading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mDownloadThread.interrupt();
|
||||||
|
mDownloadThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFileResumeInternal() {
|
||||||
|
if (!mDestination.exists()) {
|
||||||
|
mCallback.onFailure(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long offset = mDestination.length();
|
||||||
|
mClient.setRequestProperty("Range", "bytes=" + offset + "-");
|
||||||
|
downloadFileInternalCommon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFileInternalCommon(boolean resume) {
|
||||||
|
if (mDownloadThread != null) {
|
||||||
|
Log.wtf(TAG, "Already downloading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDownloadThread = new DownloadThread(resume);
|
||||||
|
mDownloadThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSuccessCode(int statusCode) {
|
||||||
|
return (statusCode / 100) == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPartialContentCode(int statusCode) {
|
||||||
|
return statusCode == 206;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownloadThread extends Thread {
|
||||||
|
|
||||||
|
private long mTotalBytes = 0;
|
||||||
|
private long mTotalBytesRead = 0;
|
||||||
|
|
||||||
|
private long mCurSampleBytes = 0;
|
||||||
|
private long mLastMillis = 0;
|
||||||
|
private long mSpeed = -1;
|
||||||
|
private long mEta = -1;
|
||||||
|
|
||||||
|
private final boolean mResume;
|
||||||
|
|
||||||
|
private DownloadThread(boolean resume) {
|
||||||
|
mResume = resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateSpeed() {
|
||||||
|
final long millis = SystemClock.elapsedRealtime();
|
||||||
|
final long delta = millis - mLastMillis;
|
||||||
|
if (delta > 500) {
|
||||||
|
final long curSpeed = ((mTotalBytesRead - mCurSampleBytes) * 1000) / delta;
|
||||||
|
if (mSpeed == -1) {
|
||||||
|
mSpeed = curSpeed;
|
||||||
|
} else {
|
||||||
|
mSpeed = ((mSpeed * 3) + curSpeed) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastMillis = millis;
|
||||||
|
mCurSampleBytes = mTotalBytesRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateEta() {
|
||||||
|
if (mSpeed > 0) {
|
||||||
|
mEta = (mTotalBytes - mTotalBytesRead) / mSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
mClient.connect();
|
||||||
|
int responseCode = mClient.getResponseCode();
|
||||||
|
mCallback.onResponse(responseCode, mClient.getURL().toString(), new Headers());
|
||||||
|
|
||||||
|
if (mResume && isPartialContentCode(responseCode)) {
|
||||||
|
mTotalBytesRead = mDestination.length();
|
||||||
|
Log.d(TAG, "The server fulfilled the partial content request");
|
||||||
|
} else if (mResume || !isSuccessCode(responseCode)) {
|
||||||
|
Log.e(TAG, "The server replied with code " + responseCode);
|
||||||
|
mCallback.onFailure(isInterrupted());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (
|
||||||
|
InputStream inputStream = mClient.getInputStream();
|
||||||
|
OutputStream outputStream = new FileOutputStream(mDestination, mResume)
|
||||||
|
) {
|
||||||
|
mTotalBytes = mClient.getContentLength() + mTotalBytesRead;
|
||||||
|
byte[] b = new byte[8192];
|
||||||
|
int count;
|
||||||
|
while (!isInterrupted() && (count = inputStream.read(b)) > 0) {
|
||||||
|
outputStream.write(b, 0, count);
|
||||||
|
mTotalBytesRead += count;
|
||||||
|
calculateSpeed();
|
||||||
|
calculateEta();
|
||||||
|
if (mProgressListener != null) {
|
||||||
|
mProgressListener.update(mTotalBytesRead, mTotalBytes, mSpeed, mEta,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mProgressListener != null) {
|
||||||
|
mProgressListener.update(mTotalBytesRead, mTotalBytes, mSpeed, mEta, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.flush();
|
||||||
|
|
||||||
|
if (isInterrupted()) {
|
||||||
|
mCallback.onFailure(true);
|
||||||
|
} else {
|
||||||
|
mCallback.onSuccess(mDestination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error downloading file", e);
|
||||||
|
mCallback.onFailure(isInterrupted());
|
||||||
|
} finally {
|
||||||
|
mClient.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,297 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 The LineageOS Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.lineageos.updater.download;
|
|
||||||
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.okhttp.Callback;
|
|
||||||
import com.android.okhttp.Interceptor;
|
|
||||||
import com.android.okhttp.MediaType;
|
|
||||||
import com.android.okhttp.OkHttpClient;
|
|
||||||
import com.android.okhttp.Request;
|
|
||||||
import com.android.okhttp.Response;
|
|
||||||
import com.android.okhttp.ResponseBody;
|
|
||||||
import com.android.okhttp.okio.Buffer;
|
|
||||||
import com.android.okhttp.okio.BufferedSink;
|
|
||||||
import com.android.okhttp.okio.BufferedSource;
|
|
||||||
import com.android.okhttp.okio.ForwardingSource;
|
|
||||||
import com.android.okhttp.okio.Okio;
|
|
||||||
import com.android.okhttp.okio.Source;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
class OkHttpDownloadClient implements DownloadClient {
|
|
||||||
|
|
||||||
private static final String TAG = "DownloadClient";
|
|
||||||
|
|
||||||
private final Object DOWNLOAD_TAG = new Object();
|
|
||||||
|
|
||||||
private final OkHttpClient mClient = new OkHttpClient();
|
|
||||||
|
|
||||||
private final String mUrl;
|
|
||||||
private final File mDestination;
|
|
||||||
private final DownloadClient.ProgressListener mProgressListener;
|
|
||||||
private final DownloadClient.DownloadCallback mCallback;
|
|
||||||
private long mResumeOffset = 0;
|
|
||||||
|
|
||||||
private boolean mDownloading = false;
|
|
||||||
private boolean mCancelled = false;
|
|
||||||
|
|
||||||
public class Headers implements DownloadClient.Headers {
|
|
||||||
private com.android.okhttp.Headers mHeaders;
|
|
||||||
|
|
||||||
private Headers(com.android.okhttp.Headers headers) {
|
|
||||||
mHeaders = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get(String name) {
|
|
||||||
return mHeaders.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> getAll() {
|
|
||||||
return mHeaders.toMultimap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OkHttpDownloadClient(String url, File destination,
|
|
||||||
DownloadClient.ProgressListener progressListener,
|
|
||||||
DownloadClient.DownloadCallback callback) {
|
|
||||||
mUrl = url;
|
|
||||||
mDestination = destination;
|
|
||||||
mProgressListener = progressListener;
|
|
||||||
mCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
if (mDownloading) {
|
|
||||||
Log.e(TAG, "Already downloading");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mCancelled = false;
|
|
||||||
mDownloading = true;
|
|
||||||
downloadFileInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resume() {
|
|
||||||
if (mDownloading) {
|
|
||||||
Log.e(TAG, "Already downloading");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mCancelled = false;
|
|
||||||
mDownloading = true;
|
|
||||||
downloadFileResumeInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
if (!mDownloading) {
|
|
||||||
Log.e(TAG, "Not downloading");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mDownloading = false;
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mCancelled = true;
|
|
||||||
mClient.cancel(DOWNLOAD_TAG);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadFileInternal() {
|
|
||||||
final Request request = new Request.Builder()
|
|
||||||
.url(mUrl)
|
|
||||||
.tag(DOWNLOAD_TAG)
|
|
||||||
.build();
|
|
||||||
downloadFileInternalCommon(request, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadFileResumeInternal() {
|
|
||||||
final Request.Builder requestBuilder = new Request.Builder()
|
|
||||||
.url(mUrl)
|
|
||||||
.tag(DOWNLOAD_TAG);
|
|
||||||
if (!mDestination.exists()) {
|
|
||||||
mCallback.onFailure(mCancelled);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long offset = mDestination.length();
|
|
||||||
requestBuilder.addHeader("Range", "bytes=" + offset + "-");
|
|
||||||
final Request request = requestBuilder.build();
|
|
||||||
downloadFileInternalCommon(request, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isSuccessCode(int statusCode) {
|
|
||||||
return (statusCode / 100) == 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPartialContentCode(int statusCode) {
|
|
||||||
return statusCode == 206;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadFileInternalCommon(final Request request, final boolean resume) {
|
|
||||||
|
|
||||||
mClient.networkInterceptors().add(new Interceptor() {
|
|
||||||
@Override
|
|
||||||
public Response intercept(Chain chain) throws IOException {
|
|
||||||
Response originalResponse = chain.proceed(chain.request());
|
|
||||||
ProgressResponseBody progressResponseBody =
|
|
||||||
new ProgressResponseBody(originalResponse.body(), mProgressListener);
|
|
||||||
return originalResponse.newBuilder()
|
|
||||||
.body(progressResponseBody)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mClient.newCall(request).enqueue(new Callback() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Request request, IOException e) {
|
|
||||||
Log.d(TAG, "Download failed", e);
|
|
||||||
mCallback.onFailure(mCancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(Response response) {
|
|
||||||
Log.d(TAG, "Downloading");
|
|
||||||
|
|
||||||
final ResponseBody body = response.body();
|
|
||||||
if (resume && isPartialContentCode(response.code())) {
|
|
||||||
mResumeOffset = mDestination.length();
|
|
||||||
Log.d(TAG, "The server fulfilled the partial content request");
|
|
||||||
} else if (resume || !isSuccessCode(response.code())) {
|
|
||||||
Log.e(TAG, "The server replied with code " + response.code());
|
|
||||||
mCallback.onFailure(mCancelled);
|
|
||||||
try {
|
|
||||||
body.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Could not close response body", e);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCallback.onResponse(response.code(), response.request().urlString(),
|
|
||||||
new Headers(response.headers()));
|
|
||||||
try (BufferedSink sink = Okio.buffer(resume ?
|
|
||||||
Okio.appendingSink(mDestination) : Okio.sink(mDestination))) {
|
|
||||||
sink.writeAll(body.source());
|
|
||||||
Log.d(TAG, "Download complete");
|
|
||||||
sink.flush();
|
|
||||||
mCallback.onSuccess(mDestination);
|
|
||||||
} catch (IOException e) {
|
|
||||||
onFailure(request, e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
body.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Could not close response body", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ProgressResponseBody extends ResponseBody {
|
|
||||||
|
|
||||||
private final ResponseBody mResponseBody;
|
|
||||||
private final DownloadClient.ProgressListener mProgressListener;
|
|
||||||
private BufferedSource mBufferedSource;
|
|
||||||
|
|
||||||
ProgressResponseBody(ResponseBody responseBody,
|
|
||||||
DownloadClient.ProgressListener progressListener) {
|
|
||||||
mResponseBody = responseBody;
|
|
||||||
mProgressListener = progressListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaType contentType() {
|
|
||||||
return mResponseBody.contentType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long contentLength() throws IOException {
|
|
||||||
return mResponseBody.contentLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BufferedSource source() throws IOException {
|
|
||||||
if (mBufferedSource == null) {
|
|
||||||
mBufferedSource = Okio.buffer(source(mResponseBody.source()));
|
|
||||||
}
|
|
||||||
return mBufferedSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Source source(Source source) {
|
|
||||||
return new ForwardingSource(source) {
|
|
||||||
private long mTotalBytes = 0;
|
|
||||||
private long mTotalBytesRead = mResumeOffset;
|
|
||||||
|
|
||||||
private long mCurSampleBytes = 0;
|
|
||||||
private long mLastMillis = 0;
|
|
||||||
private long mSpeed = -1;
|
|
||||||
private long mEta = -1;
|
|
||||||
|
|
||||||
private void calculateSpeed() {
|
|
||||||
final long millis = SystemClock.elapsedRealtime();
|
|
||||||
final long delta = millis - mLastMillis;
|
|
||||||
if (delta > 500) {
|
|
||||||
final long curSpeed = ((mTotalBytesRead - mCurSampleBytes) * 1000) / delta;
|
|
||||||
if (mSpeed == -1) {
|
|
||||||
mSpeed = curSpeed;
|
|
||||||
} else {
|
|
||||||
mSpeed = ((mSpeed * 3) + curSpeed) / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastMillis = millis;
|
|
||||||
mCurSampleBytes = mTotalBytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateEta() {
|
|
||||||
if (mSpeed > 0) {
|
|
||||||
mEta = (mTotalBytes - mTotalBytesRead) / mSpeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long read(Buffer sink, long byteCount) throws IOException {
|
|
||||||
long bytesRead = super.read(sink, byteCount);
|
|
||||||
|
|
||||||
mTotalBytes = mResponseBody.contentLength() + mResumeOffset;
|
|
||||||
// read() returns the number of bytes read, or -1 if this source is exhausted.
|
|
||||||
mTotalBytesRead += bytesRead != -1 ? bytesRead : 0;
|
|
||||||
|
|
||||||
calculateSpeed();
|
|
||||||
calculateEta();
|
|
||||||
|
|
||||||
if (mProgressListener != null) {
|
|
||||||
mProgressListener.update(mTotalBytesRead, mTotalBytes,
|
|
||||||
mSpeed, mEta, bytesRead == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user