/**
* 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.database.Cursor;
import android.util.Log;
import com.amazonaws.AmazonClientException;
import com.amazonaws.mobileconnectors.s3.transferutility.TransferService.NetworkInfoReceiver;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AbortMultipartUploadRequest;
import com.amazonaws.util.json.JsonUtils;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* TransferRecord is used to store all the information of a transfer and
* start/stop the a thread for the transfer task.
*/
class TransferRecord {
private static final String TAG = "TransferRecord";
public int id;
public int mainUploadId;
public int isRequesterPays;
public int isMultipart;
public int isLastPart;
public int isEncrypted;
public int partNumber;
public long bytesTotal;
public long bytesCurrent;
public long speed;
public long rangeStart;
public long rangeLast;
public long fileOffset;
public TransferType type;
public TransferState state;
public String bucketName;
public String key;
public String versionId;
public String file;
public String multipartId;
public String eTag;
public String headerContentType;
public String headerContentLanguage;
public String headerContentDisposition;
public String headerContentEncoding;
public String headerCacheControl;
public String headerExpire;
/**
* The following were added in 2.2.6 to support object metdata
*/
public Map<String, String> userMetadata;
public String expirationTimeRuleId;
// This is a long representing a date, however it may be null
public String httpExpires;
public String sseAlgorithm;
public String sseKMSKey;
public String md5;
public String cannedAcl;
private Future<?> submittedTask;
/**
* Constructs a TransferRecord and initializes the transfer id and S3
* client.
*
* @param id The id of a transfer.
*/
public TransferRecord(int id) {
this.id = id;
}
/**
* Updates all the fields from database using the given Cursor.
*
* @param c A Cursor pointing to a transfer record.
*/
public void updateFromDB(Cursor c) {
this.id = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_ID));
this.mainUploadId = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_MAIN_UPLOAD_ID));
this.type = TransferType.getType(c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_TYPE)));
this.state = TransferState.getState(c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_STATE)));
this.bucketName = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_BUCKET_NAME));
this.key = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_KEY));
this.versionId = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_VERSION_ID));
this.bytesTotal = c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_BYTES_TOTAL));
this.bytesCurrent = c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_BYTES_CURRENT));
this.speed = c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_SPEED));
this.isRequesterPays = c.getInt(c
.getColumnIndexOrThrow(TransferTable.COLUMN_IS_REQUESTER_PAYS));
this.isMultipart = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_IS_MULTIPART));
this.isLastPart = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_IS_LAST_PART));
this.isEncrypted = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_IS_ENCRYPTED));
this.partNumber = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_PART_NUM));
this.eTag = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_ETAG));
this.file = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_FILE));
this.multipartId = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_MULTIPART_ID));
this.rangeStart = c
.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_DATA_RANGE_START));
this.rangeLast = c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_DATA_RANGE_LAST));
this.fileOffset = c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_FILE_OFFSET));
this.headerContentType = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_CONTENT_TYPE));
this.headerContentLanguage = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_CONTENT_LANGUAGE));
this.headerContentDisposition = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_CONTENT_DISPOSITION));
this.headerContentEncoding = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_CONTENT_ENCODING));
this.headerCacheControl = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_CACHE_CONTROL));
this.headerExpire = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HEADER_EXPIRE));
this.userMetadata = JsonUtils.jsonToMap(c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_USER_METADATA)));
this.expirationTimeRuleId = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_EXPIRATION_TIME_RULE_ID));
this.httpExpires = c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_HTTP_EXPIRES_DATE));
this.sseAlgorithm = c
.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_SSE_ALGORITHM));
this.sseKMSKey = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_SSE_KMS_KEY));
this.md5 = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_CONTENT_MD5));
this.cannedAcl = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_CANNED_ACL));
}
/**
* Checks the state of the transfer and starts a thread to run the transfer
* task if possible.
*
* @param s3 s3 instance
* @param dbUtil database util
* @param updater status updater
* @param networkInfo network info
* @return Whether the task is running.
*/
public boolean start(AmazonS3 s3, TransferDBUtil dbUtil, TransferStatusUpdater updater,
NetworkInfoReceiver networkInfo) {
if (!isRunning() && checkIsReadyToRun()) {
if (type.equals(TransferType.DOWNLOAD)) {
submittedTask = TransferThreadPool
.submitTask(new DownloadTask(this, s3, updater, networkInfo));
} else {
submittedTask = TransferThreadPool
.submitTask(new UploadTask(this, s3, dbUtil, updater, networkInfo));
}
return true;
}
return false;
}
/**
* Pauses a running transfer.
*
* @param s3 s3 instance
* @param updater status updater
* @return true if the transfer is running and is paused successfully, false
* otherwise
*/
public boolean pause(AmazonS3 s3, TransferStatusUpdater updater) {
if (!isFinalState(state) && !TransferState.PAUSED.equals(state)) {
updater.updateState(id, TransferState.PAUSED);
if (isRunning()) {
submittedTask.cancel(true);
}
return true;
}
return false;
}
/**
* Cancels a running transfer.
*
* @param s3 s3 instance
* @param updater status updater
* @return true if the transfer is running and is canceled successfully,
* false otherwise
*/
public boolean cancel(final AmazonS3 s3, final TransferStatusUpdater updater) {
if (!isFinalState(state)) {
updater.updateState(id, TransferState.CANCELED);
if (isRunning()) {
submittedTask.cancel(true);
}
// additional cleanups
if (isMultipart == 1) {
new Thread(new Runnable() {
@Override
public void run() {
try {
s3.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,
key, multipartId));
Log.d(TAG, "Successfully clean up multipart upload: " + id);
} catch (AmazonClientException e) {
Log.d(TAG, "Failed to abort multiplart upload: " + id, e);
}
}
}).start();
} else if (TransferType.DOWNLOAD.equals(type)) {
// remove partially download file
new File(file).delete();
}
return true;
}
return false;
}
/**
* Checks whether the transfer is actively running
*
* @return true if the transfer is running
*/
boolean isRunning() {
return submittedTask != null && !submittedTask.isDone();
}
/**
* Wait till transfer finishes.
*
* @param timeout the maximum time to wait in milliseconds
* @throws InterruptedException
* @throws ExecutionException
* @throws TimeoutException
*/
void waitTillFinish(long timeout) throws InterruptedException, ExecutionException,
TimeoutException {
if (isRunning()) {
submittedTask.get(timeout, TimeUnit.MILLISECONDS);
}
}
/**
* Determines whether a transfer state is a final state.
*/
private boolean isFinalState(TransferState state) {
return TransferState.COMPLETED.equals(state)
|| TransferState.FAILED.equals(state)
|| TransferState.CANCELED.equals(state);
}
private boolean checkIsReadyToRun() {
return partNumber == 0 && !TransferState.COMPLETED.equals(state);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[")
.append("id:").append(id).append(",")
.append("bucketName:").append(bucketName).append(",")
.append("key:").append(key).append(",")
.append("file:").append(file).append(",")
.append("type:").append(type).append(",")
.append("bytesTotal:").append(bytesTotal).append(",")
.append("bytesCurrent:").append(bytesCurrent).append(",")
.append("fileOffset:").append(fileOffset).append(",")
.append("state:").append(state).append(",")
.append("cannedAcl:").append(cannedAcl).append(",")
.append("mainUploadId:").append(mainUploadId).append(",")
.append("isMultipart:").append(isMultipart).append(",")
.append("isLastPart:").append(isLastPart).append(",")
.append("partNumber:").append(partNumber).append(",")
.append("multipartId:").append(multipartId).append(",")
.append("eTag:").append(eTag)
.append("]");
return sb.toString();
}
}