PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java

https://gitlab.com/jnettome/AntennaPod
Java | 306 lines | 263 code | 35 blank | 8 comment | 66 complexity | afc4730f950dba0be32fd31d9220f7b8 MD5 | raw file
  1. package de.danoeh.antennapod.core.service.download;
  2. import android.util.Log;
  3. import com.squareup.okhttp.OkHttpClient;
  4. import com.squareup.okhttp.Protocol;
  5. import com.squareup.okhttp.Request;
  6. import com.squareup.okhttp.Response;
  7. import com.squareup.okhttp.ResponseBody;
  8. import com.squareup.okhttp.internal.http.HttpDate;
  9. import org.apache.commons.io.IOUtils;
  10. import org.apache.commons.lang3.StringUtils;
  11. import org.apache.http.HttpStatus;
  12. import java.io.BufferedInputStream;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.io.RandomAccessFile;
  17. import java.io.UnsupportedEncodingException;
  18. import java.net.HttpURLConnection;
  19. import java.net.SocketTimeoutException;
  20. import java.net.URI;
  21. import java.net.UnknownHostException;
  22. import java.util.Arrays;
  23. import java.util.Date;
  24. import de.danoeh.antennapod.core.ClientConfig;
  25. import de.danoeh.antennapod.core.R;
  26. import de.danoeh.antennapod.core.feed.FeedImage;
  27. import de.danoeh.antennapod.core.util.DownloadError;
  28. import de.danoeh.antennapod.core.util.StorageUtils;
  29. import de.danoeh.antennapod.core.util.URIUtil;
  30. import okio.ByteString;
  31. public class HttpDownloader extends Downloader {
  32. private static final String TAG = "HttpDownloader";
  33. private static final int BUFFER_SIZE = 8 * 1024;
  34. public HttpDownloader(DownloadRequest request) {
  35. super(request);
  36. }
  37. @Override
  38. protected void download() {
  39. File destination = new File(request.getDestination());
  40. final boolean fileExists = destination.exists();
  41. if (request.isDeleteOnFailure() && fileExists) {
  42. Log.w(TAG, "File already exists");
  43. if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
  44. onFail(DownloadError.ERROR_FILE_EXISTS, null);
  45. return;
  46. } else {
  47. onSuccess();
  48. return;
  49. }
  50. }
  51. OkHttpClient httpClient = AntennapodHttpClient.getHttpClient();
  52. RandomAccessFile out = null;
  53. InputStream connection;
  54. ResponseBody responseBody = null;
  55. try {
  56. final URI uri = URIUtil.getURIFromRequestUrl(request.getSource());
  57. Request.Builder httpReq = new Request.Builder().url(uri.toURL())
  58. .header("User-Agent", ClientConfig.USER_AGENT);
  59. if(request.getIfModifiedSince() > 0) {
  60. long threeDaysAgo = System.currentTimeMillis() - 1000*60*60*24*3;
  61. if(request.getIfModifiedSince() > threeDaysAgo) {
  62. Date date = new Date(request.getIfModifiedSince());
  63. String httpDate = HttpDate.format(date);
  64. Log.d(TAG, "addHeader(\"If-Modified-Since\", \"" + httpDate + "\")");
  65. httpReq.addHeader("If-Modified-Since", httpDate);
  66. }
  67. }
  68. // add authentication information
  69. String userInfo = uri.getUserInfo();
  70. if (userInfo != null) {
  71. String[] parts = userInfo.split(":");
  72. if (parts.length == 2) {
  73. String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
  74. httpReq.header("Authorization", credentials);
  75. }
  76. } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
  77. String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
  78. "ISO-8859-1");
  79. httpReq.header("Authorization", credentials);
  80. }
  81. // add range header if necessary
  82. if (fileExists) {
  83. request.setSoFar(destination.length());
  84. httpReq.addHeader("Range", "bytes=" + request.getSoFar() + "-");
  85. Log.d(TAG, "Adding range header: " + request.getSoFar());
  86. }
  87. Response response = null;
  88. try {
  89. response = httpClient.newCall(httpReq.build()).execute();
  90. } catch(IOException e) {
  91. Log.e(TAG, e.toString());
  92. if(e.getMessage().contains("PROTOCOL_ERROR")) {
  93. httpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
  94. response = httpClient.newCall(httpReq.build()).execute();
  95. }
  96. else {
  97. throw e;
  98. }
  99. }
  100. responseBody = response.body();
  101. String contentEncodingHeader = response.header("Content-Encoding");
  102. boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
  103. Log.d(TAG, "Response code is " + response.code());
  104. if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
  105. Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoding");
  106. if (userInfo != null) {
  107. String[] parts = userInfo.split(":");
  108. if (parts.length == 2) {
  109. String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
  110. httpReq.header("Authorization", credentials);
  111. }
  112. } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
  113. String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
  114. "UTF-8");
  115. httpReq.header("Authorization", credentials);
  116. }
  117. response = httpClient.newCall(httpReq.build()).execute();
  118. responseBody = response.body();
  119. contentEncodingHeader = response.header("Content-Encoding");
  120. isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
  121. }
  122. if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
  123. Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled");
  124. onCancelled();
  125. return;
  126. }
  127. if (!response.isSuccessful() || response.body() == null) {
  128. final DownloadError error;
  129. final String details;
  130. if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
  131. error = DownloadError.ERROR_UNAUTHORIZED;
  132. details = String.valueOf(response.code());
  133. } else {
  134. error = DownloadError.ERROR_HTTP_DATA_ERROR;
  135. details = String.valueOf(response.code());
  136. }
  137. onFail(error, details);
  138. return;
  139. }
  140. if (!StorageUtils.storageAvailable(ClientConfig.applicationCallbacks.getApplicationInstance())) {
  141. onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
  142. return;
  143. }
  144. connection = new BufferedInputStream(responseBody.byteStream());
  145. String contentRangeHeader = (fileExists) ? response.header("Content-Range") : null;
  146. if (fileExists && response.code() == HttpStatus.SC_PARTIAL_CONTENT
  147. && !StringUtils.isEmpty(contentRangeHeader)) {
  148. String start = contentRangeHeader.substring("bytes ".length(),
  149. contentRangeHeader.indexOf("-"));
  150. request.setSoFar(Long.valueOf(start));
  151. Log.d(TAG, "Starting download at position " + request.getSoFar());
  152. out = new RandomAccessFile(destination, "rw");
  153. out.seek(request.getSoFar());
  154. } else {
  155. destination.delete();
  156. destination.createNewFile();
  157. out = new RandomAccessFile(destination, "rw");
  158. }
  159. byte[] buffer = new byte[BUFFER_SIZE];
  160. int count = 0;
  161. request.setStatusMsg(R.string.download_running);
  162. Log.d(TAG, "Getting size of download");
  163. request.setSize(responseBody.contentLength() + request.getSoFar());
  164. Log.d(TAG, "Size is " + request.getSize());
  165. if (request.getSize() < 0) {
  166. request.setSize(DownloadStatus.SIZE_UNKNOWN);
  167. }
  168. long freeSpace = StorageUtils.getFreeSpaceAvailable();
  169. Log.d(TAG, "Free space is " + freeSpace);
  170. if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
  171. && request.getSize() > freeSpace) {
  172. onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
  173. return;
  174. }
  175. Log.d(TAG, "Starting download");
  176. try {
  177. while (!cancelled
  178. && (count = connection.read(buffer)) != -1) {
  179. out.write(buffer, 0, count);
  180. request.setSoFar(request.getSoFar() + count);
  181. request.setProgressPercent((int) (((double) request
  182. .getSoFar() / (double) request
  183. .getSize()) * 100));
  184. }
  185. } catch(IOException e) {
  186. Log.e(TAG, Log.getStackTraceString(e));
  187. }
  188. if (cancelled) {
  189. onCancelled();
  190. } else {
  191. // check if size specified in the response header is the same as the size of the
  192. // written file. This check cannot be made if compression was used
  193. if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
  194. request.getSoFar() != request.getSize()) {
  195. onFail(DownloadError.ERROR_IO_ERROR,
  196. "Download completed but size: " +
  197. request.getSoFar() +
  198. " does not equal expected size " +
  199. request.getSize()
  200. );
  201. return;
  202. } else if(request.getSize() > 0 && request.getSoFar() == 0){
  203. onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read");
  204. return;
  205. }
  206. onSuccess();
  207. }
  208. } catch (IllegalArgumentException e) {
  209. e.printStackTrace();
  210. onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
  211. } catch (SocketTimeoutException e) {
  212. e.printStackTrace();
  213. onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
  214. } catch (UnknownHostException e) {
  215. e.printStackTrace();
  216. onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
  217. } catch (IOException e) {
  218. e.printStackTrace();
  219. onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
  220. } catch (NullPointerException e) {
  221. // might be thrown by connection.getInputStream()
  222. e.printStackTrace();
  223. onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
  224. } finally {
  225. IOUtils.closeQuietly(out);
  226. AntennapodHttpClient.cleanup();
  227. IOUtils.closeQuietly(responseBody);
  228. }
  229. }
  230. private void onSuccess() {
  231. Log.d(TAG, "Download was successful");
  232. result.setSuccessful();
  233. }
  234. private void onFail(DownloadError reason, String reasonDetailed) {
  235. Log.d(TAG, "Download failed");
  236. result.setFailed(reason, reasonDetailed);
  237. if (request.isDeleteOnFailure()) {
  238. cleanup();
  239. }
  240. }
  241. private void onCancelled() {
  242. Log.d(TAG, "Download was cancelled");
  243. result.setCancelled();
  244. cleanup();
  245. }
  246. /**
  247. * Deletes unfinished downloads.
  248. */
  249. private void cleanup() {
  250. if (request.getDestination() != null) {
  251. File dest = new File(request.getDestination());
  252. if (dest.exists()) {
  253. boolean rc = dest.delete();
  254. Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
  255. + rc);
  256. } else {
  257. Log.d(TAG, "cleanup() didn't delete file: does not exist.");
  258. }
  259. }
  260. }
  261. public static String encodeCredentials(String username, String password, String charset) {
  262. try {
  263. String credentials = username + ":" + password;
  264. byte[] bytes = credentials.getBytes(charset);
  265. String encoded = ByteString.of(bytes).base64();
  266. return "Basic " + encoded;
  267. } catch (UnsupportedEncodingException e) {
  268. throw new AssertionError();
  269. }
  270. }
  271. }