/** * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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 com.amazonaws.mobileconnectors.s3.transferutility; import android.util.Log; import com.amazonaws.AmazonClientException; import com.amazonaws.mobileconnectors.s3.transferutility.TransferService.NetworkInfoReceiver; import com.amazonaws.retry.RetryUtils; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.Callable; /** * Performs download operation and returns a Boolean value indicating whether * the file has been downloaded successfully. */ class DownloadTask implements Callable<Boolean> { private static final String TAG = "DownloadTask"; private final AmazonS3 s3; private final TransferRecord download; private final TransferStatusUpdater updater; private final NetworkInfoReceiver networkInfo; /** * Constructs a DownloadTask with the given download info and S3 client. * * @param download A TransferRecord object storing all the information of * the download * @param s3 Low-level S3 client * @param updater status updater * @param networkInfo network info */ public DownloadTask(TransferRecord download, AmazonS3 s3, TransferStatusUpdater updater, NetworkInfoReceiver networkInfo) { this.download = download; this.s3 = s3; this.updater = updater; this.networkInfo = networkInfo; } /** * Runs download task and returns whether successfully downloaded. */ @Override public Boolean call() throws Exception { if (!networkInfo.isNetworkConnected()) { updater.updateState(download.id, TransferState.WAITING_FOR_NETWORK); return false; } updater.updateState(download.id, TransferState.IN_PROGRESS); final GetObjectRequest getObjectRequest = new GetObjectRequest(download.bucketName, download.key); TransferUtility.appendTransferServiceUserAgentString(getObjectRequest); File file = new File(download.file); long bytesCurrent = file.length(); if (bytesCurrent > 0) { Log.d(TAG, String.format("Resume transfer %d from %d bytes", download.id, bytesCurrent)); /* * Setting the last byte position to -1 means downloading the object * from bytesCurrent to the end. */ getObjectRequest.setRange(bytesCurrent, -1); } getObjectRequest.setGeneralProgressListener(updater.newProgressListener(download.id)); try { S3Object object = s3.getObject(getObjectRequest); if (object == null) { updater.throwError(download.id, new IllegalStateException( "AmazonS3.getObject returns null")); updater.updateState(download.id, TransferState.FAILED); return false; } long bytesTotal = object.getObjectMetadata().getInstanceLength(); updater.updateProgress(download.id, bytesCurrent, bytesTotal); saveToFile(object.getObjectContent(), file); updater.updateProgress(download.id, bytesTotal, bytesTotal); updater.updateState(download.id, TransferState.COMPLETED); return true; } catch (Exception e) { if (RetryUtils.isInterrupted(e)) { /* * thread is interrupted by user. don't update the state as it's * set by caller who interrupted */ Log.d(TAG, "Transfer " + download.id + " is interrupted by user"); } else if (e.getCause() != null && e.getCause() instanceof IOException && !networkInfo.isNetworkConnected()) { Log.d(TAG, "Transfer " + download.id + " waits for network"); updater.updateState(download.id, TransferState.WAITING_FOR_NETWORK); } else { Log.e(TAG, "Failed to download: " + download.id + " due to " + e.getMessage()); updater.throwError(download.id, e); updater.updateState(download.id, TransferState.FAILED); } } return false; } /** * Writes stream data into a file. * * @param is input stream * @param file file to be written */ private void saveToFile(InputStream is, File file) { // attempt to create the parent if it doesn't exist File parentDirectory = file.getParentFile(); if (parentDirectory != null && !parentDirectory.exists()) { parentDirectory.mkdirs(); } boolean append = file.length() > 0; OutputStream os = null; try { os = new BufferedOutputStream(new FileOutputStream(file, append)); byte[] buffer = new byte[1024 * 16]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } catch (IOException e) { throw new AmazonClientException( "Unable to store object contents to disk: " + e.getMessage(), e); } finally { try { if (os != null) { os.close(); } } catch (IOException ioe) { // ignore } try { is.close(); } catch (IOException ioe) { // ignore } } } }