blob: e3a195253b78a3d840ef7a5b192f28609565db54 [file] [log] [blame]
package de.danoeh.antennapod.core.service.download;
import android.text.TextUtils;
import android.util.Log;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URIUtil;
import okio.ByteString;
public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader";
private static final int BUFFER_SIZE = 8 * 1024;
public HttpDownloader(DownloadRequest request) {
super(request);
}
@Override
protected void download() {
File destination = new File(request.getDestination());
final boolean fileExists = destination.exists();
if (request.isDeleteOnFailure() && fileExists) {
Log.w(TAG, "File already exists");
if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
onFail(DownloadError.ERROR_FILE_EXISTS, null);
return;
} else {
onSuccess();
return;
}
}
OkHttpClient httpClient = AntennapodHttpClient.getHttpClient();
RandomAccessFile out = null;
InputStream connection;
ResponseBody responseBody = null;
try {
final URI uri = URIUtil.getURIFromRequestUrl(request.getSource());
Request.Builder httpReq = new Request.Builder().url(uri.toURL())
.header("User-Agent", ClientConfig.USER_AGENT);
if(!TextUtils.isEmpty(request.getLastModified())) {
String lastModified = request.getLastModified();
Date lastModifiedDate = DateUtils.parse(lastModified);
if(lastModifiedDate != null) {
long threeDaysAgo = System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 3;
if (lastModifiedDate.getTime() > threeDaysAgo) {
Log.d(TAG, "addHeader(\"If-Modified-Since\", \"" + lastModified + "\")");
httpReq.addHeader("If-Modified-Since", lastModified);
}
} else {
String eTag = lastModified;
Log.d(TAG, "addHeader(\"If-None-Match\", \"" + eTag + "\")");
httpReq.addHeader("If-None-Match", eTag);
}
}
// add authentication information
String userInfo = uri.getUserInfo();
if (userInfo != null) {
String[] parts = userInfo.split(":");
if (parts.length == 2) {
String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
} else if (!TextUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
"ISO-8859-1");
httpReq.header("Authorization", credentials);
}
// add range header if necessary
if (fileExists) {
request.setSoFar(destination.length());
httpReq.addHeader("Range", "bytes=" + request.getSoFar() + "-");
Log.d(TAG, "Adding range header: " + request.getSoFar());
}
Response response = null;
try {
response = httpClient.newCall(httpReq.build()).execute();
} catch(IOException e) {
Log.e(TAG, e.toString());
if(e.getMessage().contains("PROTOCOL_ERROR")) {
httpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
response = httpClient.newCall(httpReq.build()).execute();
}
else {
throw e;
}
}
responseBody = response.body();
String contentEncodingHeader = response.header("Content-Encoding");
boolean isGzip = false;
if(!TextUtils.isEmpty(contentEncodingHeader)) {
isGzip = TextUtils.equals(contentEncodingHeader.toLowerCase(), "gzip");
}
Log.d(TAG, "Response code is " + response.code());
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoding");
if (userInfo != null) {
String[] parts = userInfo.split(":");
if (parts.length == 2) {
String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
httpReq.header("Authorization", credentials);
}
} else if (!TextUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
"UTF-8");
httpReq.header("Authorization", credentials);
}
response = httpClient.newCall(httpReq.build()).execute();
responseBody = response.body();
contentEncodingHeader = response.header("Content-Encoding");
if(!TextUtils.isEmpty(contentEncodingHeader)) {
isGzip = TextUtils.equals(contentEncodingHeader.toLowerCase(), "gzip");
}
}
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled");
onCancelled();
return;
}
if (!response.isSuccessful() || response.body() == null) {
final DownloadError error;
final String details;
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
error = DownloadError.ERROR_UNAUTHORIZED;
details = String.valueOf(response.code());
} else {
error = DownloadError.ERROR_HTTP_DATA_ERROR;
details = String.valueOf(response.code());
}
onFail(error, details);
return;
}
if (!StorageUtils.storageAvailable()) {
onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
return;
}
connection = new BufferedInputStream(responseBody.byteStream());
String contentRangeHeader = (fileExists) ? response.header("Content-Range") : null;
if (fileExists && response.code() == HttpURLConnection.HTTP_PARTIAL
&& !TextUtils.isEmpty(contentRangeHeader)) {
String start = contentRangeHeader.substring("bytes ".length(),
contentRangeHeader.indexOf("-"));
request.setSoFar(Long.valueOf(start));
Log.d(TAG, "Starting download at position " + request.getSoFar());
out = new RandomAccessFile(destination, "rw");
out.seek(request.getSoFar());
} else {
destination.delete();
destination.createNewFile();
out = new RandomAccessFile(destination, "rw");
}
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
request.setStatusMsg(R.string.download_running);
Log.d(TAG, "Getting size of download");
request.setSize(responseBody.contentLength() + request.getSoFar());
Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
Log.d(TAG, "Free space is " + freeSpace);
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
&& request.getSize() > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
return;
}
Log.d(TAG, "Starting download");
try {
while (!cancelled
&& (count = connection.read(buffer)) != -1) {
out.write(buffer, 0, count);
request.setSoFar(request.getSoFar() + count);
request.setProgressPercent((int) (((double) request
.getSoFar() / (double) request
.getSize()) * 100));
}
} catch(IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
if (cancelled) {
onCancelled();
} else {
// check if size specified in the response header is the same as the size of the
// written file. This check cannot be made if compression was used
if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
request.getSoFar() != request.getSize()) {
onFail(DownloadError.ERROR_IO_ERROR,
"Download completed but size: " +
request.getSoFar() +
" does not equal expected size " +
request.getSize()
);
return;
} else if(request.getSize() > 0 && request.getSoFar() == 0){
onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read");
return;
}
String lastModified = response.header("Last-Modified");
if(lastModified != null) {
request.setLastModified(lastModified);
} else {
request.setLastModified(response.header("ETag"));
}
onSuccess();
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
} catch (SocketTimeoutException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
} catch (UnknownHostException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
} catch (IOException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(out);
AntennapodHttpClient.cleanup();
IOUtils.closeQuietly(responseBody);
}
}
private void onSuccess() {
Log.d(TAG, "Download was successful");
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
Log.d(TAG, "Download failed");
result.setFailed(reason, reasonDetailed);
if (request.isDeleteOnFailure()) {
cleanup();
}
}
private void onCancelled() {
Log.d(TAG, "Download was cancelled");
result.setCancelled();
cleanup();
}
/**
* Deletes unfinished downloads.
*/
private void cleanup() {
if (request.getDestination() != null) {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ rc);
} else {
Log.d(TAG, "cleanup() didn't delete file: does not exist.");
}
}
}
public static String encodeCredentials(String username, String password, String charset) {
try {
String credentials = username + ":" + password;
byte[] bytes = credentials.getBytes(charset);
String encoded = ByteString.of(bytes).base64();
return "Basic " + encoded;
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}
}