/**
* 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.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.UploadPartRequest;
import com.amazonaws.util.json.JsonUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Provides methods to conveniently perform database operations.
*/
class TransferDBUtil {
/**
* transferDBBase is a basic helper for accessing the database
*/
private static TransferDBBase transferDBBase;
/**
* Constructs a TransferDBUtil with the given Context.
*
* @param context An instance of Context.
*/
public TransferDBUtil(Context context) {
if (transferDBBase == null) {
transferDBBase = new TransferDBBase(context);
}
}
/**
* Closes the DB Connection
*/
public void closeDB() {
if (transferDBBase != null) {
transferDBBase.closeDBHelper();
}
}
/**
* Inserts a part upload record into database with the given values.
*
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @param fileOffset The byte offset for the file to upload.
* @param partNumber The part number of this part.
* @param uploadId The multipart upload id of the upload.
* @param bytesTotal The Total bytes of the file.
* @param isLastPart Whether this part is the last part of the upload.
* @return An Uri of the record inserted.
*/
public Uri insertMultipartUploadRecord(String bucket, String key, File file,
long fileOffset, int partNumber, String uploadId, long bytesTotal, int isLastPart) {
ContentValues values = generateContentValuesForMultiPartUpload(bucket, key, file,
fileOffset, partNumber, uploadId, bytesTotal, isLastPart, new ObjectMetadata(),
null);
return transferDBBase.insert(transferDBBase.getContentUri(), values);
}
/**
* Inserts a transfer record into database with the given values.
*
* @param type The type of the transfer, can be "upload" or "download".
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @param metadata The S3 Object metadata associated with this object
* @return An Uri of the record inserted.
*/
public Uri insertSingleTransferRecord(TransferType type, String bucket, String key, File file,
ObjectMetadata metadata) {
return insertSingleTransferRecord(type, bucket, key, file, metadata, null);
}
/**
* Inserts a transfer record into database with the given values.
*
* @param type The type of the transfer, can be "upload" or "download".
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @param metadata The S3 Object metadata associated with this object
* @param cannedAcl The canned Acl of this S3 object
* @return An Uri of the record inserted.
*/
public Uri insertSingleTransferRecord(TransferType type, String bucket, String key, File file,
ObjectMetadata metadata, CannedAccessControlList cannedAcl) {
ContentValues values = generateContentValuesForSinglePartTransfer(type, bucket, key, file,
metadata, cannedAcl);
return transferDBBase.insert(transferDBBase.getContentUri(), values);
}
/**
* Inserts a transfer record into database with the given values.
*
* @param type The type of the transfer, can be "upload" or "download".
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @return An Uri of the record inserted.
*/
public Uri insertSingleTransferRecord(TransferType type, String bucket, String key, File file) {
return insertSingleTransferRecord(type, bucket, key, file,
new ObjectMetadata());
}
/**
* Inserts multiple records at a time.
*
* @param valuesArray An array of values to insert.
* @return The mainUploadId of the multipart records
*/
public int bulkInsertTransferRecords(ContentValues[] valuesArray) {
return transferDBBase.bulkInsert(transferDBBase.getContentUri(), valuesArray);
}
/**
* Writes transfer status including transfer state, current transferred
* bytes and total bytes into database.
*
* @param transfer a TransferRecord object
* @return Number of rows updated.
*/
public int updateTransferRecord(TransferRecord transfer) {
ContentValues cv = new ContentValues();
cv.put(TransferTable.COLUMN_ID, transfer.id);
cv.put(TransferTable.COLUMN_STATE, transfer.state.toString());
cv.put(TransferTable.COLUMN_BYTES_TOTAL, transfer.bytesTotal);
cv.put(TransferTable.COLUMN_BYTES_CURRENT, transfer.bytesCurrent);
return transferDBBase.update(getRecordUri(transfer.id), cv, null, null);
}
/**
* Updates the current bytes of a transfer record.
*
* @param id The id of the transfer
* @param bytes The bytes currently transferred
* @return Number of rows updated.
*/
public int updateBytesTransferred(int id, long bytes) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_BYTES_CURRENT, bytes);
return transferDBBase.update(getRecordUri(id), values, null, null);
}
/**
* Updates the total bytes of a download record.
*
* @param id The id of the transfer
* @param bytes The total bytes of the download.
* @return Number of rows updated.
*/
public int updateBytesTotalForDownload(int id, long bytes) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_BYTES_TOTAL, bytes);
return transferDBBase.update(getRecordUri(id), values, null, null);
}
/**
* Updates the state but do not notify TransferService to refresh its
* transfer record list. Therefore, only TransferObserver knows the state
* change of the transfer record. If the new state is STATE_FAILED, we need
* to check the original state, because "pause", "cancel" and
* "disconnect network" actions may also cause failure message of the
* threads, but these are not actual failure of transfers.
*
* @param id The id of the transfer.
* @param state The new state of the transfer.
* @return Number of rows updated.
*/
public int updateState(int id, TransferState state) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, state.toString());
if (TransferState.FAILED.equals(state)) {
return transferDBBase.update(getRecordUri(id), values, TransferTable.COLUMN_STATE
+ " not in (?,?,?,?,?) ", new String[] {
TransferState.COMPLETED.toString(),
TransferState.PENDING_NETWORK_DISCONNECT.toString(),
TransferState.PAUSED.toString(),
TransferState.CANCELED.toString(),
TransferState.WAITING_FOR_NETWORK.toString()
});
} else {
return transferDBBase.update(getRecordUri(id), values, null, null);
}
}
/**
* Updates the state and also notify TransferService to refresh its transfer
* record list. The method is called by TransferUtility, more typically, by
* applications to perform "pause" or "resume" actions, so it needs to
* explicitly notify the Service after updating the database.
*
* @param id The id of the transfer.
* @param state The new state of the transfer.
* @return Number of rows updated.
*/
public int updateStateAndNotifyUpdate(int id, TransferState state) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, state.toString());
return transferDBBase.update(transferDBBase.getContentUri(), values,
TransferTable.COLUMN_ID + "=" + id, null);
}
/**
* Updates the multipart id of the transfer record.
*
* @param id The id of the transfer.
* @param multipartId The multipart id of the transfer.
* @return Number of rows updated.
*/
public int updateMultipartId(int id, String multipartId) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_MULTIPART_ID, multipartId);
return transferDBBase.update(getRecordUri(id), values, null, null);
}
/**
* Updates the Etag of the transfer record.
*
* @param id The id of the transfer.
* @param etag The Etag of the transfer.
* @return Number of rows updated.
*/
public int updateETag(int id, String etag) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_ETAG, etag);
return transferDBBase.update(getRecordUri(id), values, null, null);
}
/**
* Updates states of all transfer records which are "running" and "waiting"
* to "network disconnect"
*
* @return Number of rows updated.
*/
public int updateNetworkDisconnected() {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE,
TransferState.PENDING_NETWORK_DISCONNECT.toString());
return transferDBBase.update(transferDBBase.getContentUri(), values,
TransferTable.COLUMN_STATE
+ " in (?,?,?)", new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString()
});
}
/**
* Updates states of all transfer records which are "waiting for network" to
* "waiting to resume"
*
* @return Number of rows updated.
*/
public int updateNetworkConnected() {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, TransferState.RESUMED_WAITING.toString());
return transferDBBase.update(transferDBBase.getContentUri(), values,
TransferTable.COLUMN_STATE
+ " in (?,?)", new String[] {
TransferState.PENDING_NETWORK_DISCONNECT.toString(),
TransferState.WAITING_FOR_NETWORK.toString()
});
}
/**
* Updates states of all transfer records which are "running" and "waiting"
* to "paused"
*
* @return Number of rows updated.
*/
public int setAllRunningRecordsToPausedBeforeShutdownService() {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, TransferState.PAUSED.toString());
return transferDBBase.update(
transferDBBase.getContentUri(),
values,
TransferTable.COLUMN_STATE
+ " in (?,?,?,?)",
new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.PENDING_PAUSE.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString()
});
}
/**
* Updates states of all transfer records with the specified type which are
* "running" and "waiting" to "pending pause".
*
* @param TransferType The type of transfers to query for.
* @return Number of rows updated.
*/
public int pauseAllWithType(TransferType type) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, TransferState.PENDING_PAUSE.toString());
String selection = null;
String[] selectionArgs = null;
if (type == TransferType.ANY) {
selection = TransferTable.COLUMN_STATE + " in (?,?,?)";
selectionArgs = new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString()
};
} else {
selection = TransferTable.COLUMN_STATE + " in (?,?,?) and " + TransferTable.COLUMN_TYPE
+ "=?";
selectionArgs = new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString(),
type.toString()
};
}
return transferDBBase.update(transferDBBase.getContentUri(), values, selection,
selectionArgs);
}
/**
* Updates states of all transfer records with the specified which are
* "running" and "waiting" to "pending cancel"
*
* @param TransferType The type of transfers to cancel
* @return Number of rows updated.
*/
public int cancelAllWithType(TransferType type) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_STATE, TransferState.PENDING_CANCEL.toString());
String selection = null;
String[] selectionArgs = null;
if (type == TransferType.ANY) {
selection = TransferTable.COLUMN_STATE + " in (?,?,?,?,?)";
selectionArgs = new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString(),
TransferState.PAUSED.toString(),
TransferState.WAITING_FOR_NETWORK.toString()
};
} else {
selection = TransferTable.COLUMN_STATE + " in (?,?,?,?,?) and "
+ TransferTable.COLUMN_TYPE + "=?";
selectionArgs = new String[] {
TransferState.IN_PROGRESS.toString(),
TransferState.RESUMED_WAITING.toString(),
TransferState.WAITING.toString(),
TransferState.PAUSED.toString(),
TransferState.WAITING_FOR_NETWORK.toString(),
type.toString()
};
}
return transferDBBase.update(transferDBBase.getContentUri(), values, selection,
selectionArgs);
}
/**
* Queries all the records which have the given type.
*
* @param TransferType The type of transfers to query for.
* @return A Cursor pointing to records in the database with the given type.
*/
public Cursor queryAllTransfersWithType(TransferType type) {
if (type == TransferType.ANY) {
return transferDBBase.query(transferDBBase.getContentUri(), null, null, null, null);
} else {
return transferDBBase.query(transferDBBase.getContentUri(), null,
TransferTable.COLUMN_TYPE + "=?", new String[] {
type.toString()
}, null);
}
}
/**
* Queries all the records which have the given type and state.
*
* @param TransferType The type of transfers to query for.
* @param TransferState The state of the transfer.
* @return A Cursor pointing to records in the database with the given type
* and state.
*/
public Cursor queryTransfersWithTypeAndState(TransferType type, TransferState state) {
if (type == TransferType.ANY) {
return transferDBBase.query(getStateUri(state), null, null,
null, null);
} else {
return transferDBBase.query(getStateUri(state), null, TransferTable.COLUMN_TYPE + "=?",
new String[] {
type.toString()
}, null);
}
}
/**
* Queries the transfer record specified by id.
*
* @param id The id of the transfer.
* @return The result Cursor of the query.
*/
public Cursor queryTransferById(int id) {
return transferDBBase.query(getRecordUri(id), null, null, null, null);
}
/**
* Queries the transfer record specified by main upload id.
*
* @param mainUploadId The mainUploadId of a multipart upload task
* @return The bytes already uploaded for this multipart upload task
*/
public long queryBytesTransferredByMainUploadId(int mainUploadId) {
Cursor c = transferDBBase.query(getPartUri(mainUploadId), null, null, null, null);
long bytesTotal = 0;
try {
while (c.moveToNext()) {
String state = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_STATE));
if (TransferState.PART_COMPLETED.equals(TransferState.getState(state))) {
bytesTotal += c.getLong(c
.getColumnIndexOrThrow(TransferTable.COLUMN_BYTES_TOTAL));
}
}
} finally {
c.close();
}
return bytesTotal;
}
/**
* Deletes the record with the given id.
*
* @param id The id of the transfer to be deleted.
* @return Number of rows deleted.
*/
public int deleteTransferRecords(int id) {
return transferDBBase.delete(getRecordUri(id), null, null);
}
/**
* Queries all the PartETags of completed parts from the multipart upload
* specified by the mainUploadId. The list of PartETags is used to complete
* a multiart upload, so it's usually called after all partUpload tasks are
* finished.
*
* @param mainUploadId The mainUploadId of a multipart upload task
* @return A list of PartEtag of completed parts
*/
public List<PartETag> queryPartETagsOfUpload(int mainUploadId) {
List<PartETag> partETags = new ArrayList<PartETag>();
Cursor c = transferDBBase.query(getPartUri(mainUploadId), null, null, null, null);
int partNum = 0;
String eTag = null;
try {
while (c.moveToNext()) {
partNum = c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_PART_NUM));
eTag = c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_ETAG));
partETags.add(new PartETag(partNum, eTag));
}
} finally {
c.close();
}
return partETags;
}
/**
* Queries uncompleted partUpload tasks of a multipart upload and constructs
* a UploadPartRequest for each task. It's used when resuming a multipart
* upload
*
* @param mainUploadId The mainUploadId of a multipart upload task
* @param multipartId The multipartId of a multipart upload task
* @return A list of UploadPartRequest
*/
public List<UploadPartRequest> getNonCompletedPartRequestsFromDB(int mainUploadId,
String multipartId) {
ArrayList<UploadPartRequest> list = new ArrayList<UploadPartRequest>();
Cursor c = transferDBBase.query(getPartUri(mainUploadId), null, null, null, null);
try {
while (c.moveToNext()) {
if (TransferState.PART_COMPLETED.equals(TransferState.getState(c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_STATE))))) {
continue;
}
UploadPartRequest putPartRequest = new UploadPartRequest()
.withId(c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_ID)))
.withMainUploadId(
c.getInt(c
.getColumnIndexOrThrow(TransferTable.COLUMN_MAIN_UPLOAD_ID)))
.withBucketName(
c.getString(c
.getColumnIndexOrThrow(TransferTable.COLUMN_BUCKET_NAME)))
.withKey(c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_KEY)))
.withUploadId(multipartId)
.withFile(new File(
c.getString(c.getColumnIndexOrThrow(TransferTable.COLUMN_FILE))))
.withFileOffset(
c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_FILE_OFFSET)))
.withPartNumber(
c.getInt(c.getColumnIndexOrThrow(TransferTable.COLUMN_PART_NUM)))
.withPartSize(
c.getLong(c.getColumnIndexOrThrow(TransferTable.COLUMN_BYTES_TOTAL)))
.withLastPart(1 == c.getInt(c
.getColumnIndexOrThrow(TransferTable.COLUMN_IS_LAST_PART)));
list.add(putPartRequest);
}
} finally {
c.close();
}
return list;
}
/**
* Generates a ContentValues object to insert into the database with the
* given values for a multipart upload record.
*
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @param fileOffset The byte offset for the file to upload.
* @param partNumber The part number of this part.
* @param uploadId The multipart upload id of the upload.
* @param bytesTotal The Total bytes of the file.
* @param isLastPart Whether this part is the last part of the upload.
* @param metadata The S3 ObjectMetadata to send along with the object
* @param cannedAcl The canned ACL associated with the object
* @return The ContentValues object generated.
*/
public ContentValues generateContentValuesForMultiPartUpload(String bucket,
String key, File file, long fileOffset, int partNumber, String uploadId,
long bytesTotal, int isLastPart, ObjectMetadata metadata,
CannedAccessControlList cannedAcl) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_TYPE, TransferType.UPLOAD.toString());
values.put(TransferTable.COLUMN_STATE, TransferState.WAITING.toString());
values.put(TransferTable.COLUMN_BUCKET_NAME, bucket);
values.put(TransferTable.COLUMN_KEY, key);
values.put(TransferTable.COLUMN_FILE, file.getAbsolutePath());
values.put(TransferTable.COLUMN_BYTES_CURRENT, 0l);
values.put(TransferTable.COLUMN_BYTES_TOTAL, bytesTotal);
values.put(TransferTable.COLUMN_IS_MULTIPART, 1);
values.put(TransferTable.COLUMN_PART_NUM, partNumber);
values.put(TransferTable.COLUMN_FILE_OFFSET, fileOffset);
values.put(TransferTable.COLUMN_MULTIPART_ID, uploadId);
values.put(TransferTable.COLUMN_IS_LAST_PART, isLastPart);
values.put(TransferTable.COLUMN_IS_ENCRYPTED, 0);
values.putAll(generateContentValuesForObjectMetadata(metadata));
if (cannedAcl != null) {
values.put(TransferTable.COLUMN_CANNED_ACL, cannedAcl.toString());
}
return values;
}
/**
* Adds mappings to a ContentValues object for the data in the passed in
* ObjectMetadata
*
* @param metadata The ObjectMetadata the content values should be filled
* with
* @return the ContentValues
*/
private ContentValues generateContentValuesForObjectMetadata(ObjectMetadata metadata) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_USER_METADATA,
JsonUtils.mapToString(metadata.getUserMetadata()));
values.put(TransferTable.COLUMN_HEADER_CONTENT_TYPE, metadata.getContentType());
values.put(TransferTable.COLUMN_HEADER_CONTENT_ENCODING, metadata.getContentEncoding());
values.put(TransferTable.COLUMN_HEADER_CACHE_CONTROL, metadata.getCacheControl());
values.put(TransferTable.COLUMN_CONTENT_MD5, metadata.getContentMD5());
values.put(TransferTable.COLUMN_HEADER_CONTENT_DISPOSITION,
metadata.getContentDisposition());
values.put(TransferTable.COLUMN_SSE_ALGORITHM, metadata.getSSEAlgorithm());
values.put(TransferTable.COLUMN_SSE_KMS_KEY, metadata.getSSEKMSKeyId());
values.put(TransferTable.COLUMN_EXPIRATION_TIME_RULE_ID, metadata.getExpirationTimeRuleId());
if (metadata.getHttpExpiresDate() != null) {
values.put(TransferTable.COLUMN_HTTP_EXPIRES_DATE,
String.valueOf(metadata.getHttpExpiresDate().getTime()));
}
return values;
}
/**
* Generates a ContentValues object to insert into the database with the
* given values for a single chunk upload or download.
*
* @param type The type of the transfer, can be "upload" or "download".
* @param bucket The name of the bucket to upload to.
* @param key The key in the specified bucket by which to store the new
* object.
* @param file The file to upload.
* @param metadata The S3 ObjectMetadata to send along with the object
* @param cannedAcl The canned ACL associated with the object
* @return The ContentValues object generated.
*/
private ContentValues generateContentValuesForSinglePartTransfer(TransferType type,
String bucket, String key, File file, ObjectMetadata metadata,
CannedAccessControlList cannedAcl) {
ContentValues values = new ContentValues();
values.put(TransferTable.COLUMN_TYPE, type.toString());
values.put(TransferTable.COLUMN_STATE, TransferState.WAITING.toString());
values.put(TransferTable.COLUMN_BUCKET_NAME, bucket);
values.put(TransferTable.COLUMN_KEY, key);
values.put(TransferTable.COLUMN_FILE, file.getAbsolutePath());
values.put(TransferTable.COLUMN_BYTES_CURRENT, 0l);
if (type.equals(TransferType.UPLOAD))
values.put(TransferTable.COLUMN_BYTES_TOTAL, file == null ? 0l : file.length());
values.put(TransferTable.COLUMN_IS_MULTIPART, 0);
values.put(TransferTable.COLUMN_PART_NUM, 0);
values.put(TransferTable.COLUMN_IS_ENCRYPTED, 0);
values.putAll(generateContentValuesForObjectMetadata(metadata));
if (cannedAcl != null) {
values.put(TransferTable.COLUMN_CANNED_ACL, cannedAcl.toString());
}
return values;
}
/**
* Gets the Uri of the transfer record table.
*
* @return The Uri of a table.
*/
public Uri getContentUri() {
return transferDBBase.getContentUri();
}
/**
* Gets the Uri of a record.
*
* @param id The id of the transfer.
* @return The Uri of the record specified by the id.
*/
public Uri getRecordUri(int id) {
return Uri.parse(transferDBBase.getContentUri() + "/" + id);
}
/**
* Gets the Uri of part records of a multipart upload.
*
* @param mainUploadId The main upload id of the transfer.
* @return The Uri of the part upload records that have the given
* mainUploadId value.
*/
public Uri getPartUri(int mainUploadId) {
return Uri.parse(transferDBBase.getContentUri() + "/part/" + mainUploadId);
}
/**
* Gets the Uri of the records that have the given state.
*
* @param state The state of transfers
* @return The Uri that is used to query transfer records with the given
* state.
*/
public Uri getStateUri(TransferState state) {
return Uri.parse(transferDBBase.getContentUri() + "/state/" + state.toString());
}
/**
* Gets the TransferRecord by id.
*
* @param id transfer id
* @return a TransferRecord if exists, null otherwise
*/
TransferRecord getTransferById(int id) {
TransferRecord transfer = null;
Cursor c = queryTransferById(id);
try {
if (c.moveToFirst()) {
transfer = new TransferRecord(0);
transfer.updateFromDB(c);
}
} finally {
c.close();
}
return transfer;
}
}