diff --git a/app/src/main/java/com/alibaba/oss/app/service/OssService.java b/app/src/main/java/com/alibaba/oss/app/service/OssService.java
index b565c4fb..67b40849 100755
--- a/app/src/main/java/com/alibaba/oss/app/service/OssService.java
+++ b/app/src/main/java/com/alibaba/oss/app/service/OssService.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Environment;
import android.util.Log;
import com.alibaba.oss.app.Config;
@@ -14,13 +13,12 @@
import com.alibaba.sdk.android.oss.ServiceException;
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback;
import com.alibaba.sdk.android.oss.callback.OSSProgressCallback;
-import com.alibaba.sdk.android.oss.common.OSSConstants;
import com.alibaba.sdk.android.oss.common.OSSLog;
import com.alibaba.sdk.android.oss.common.auth.OSSCustomSignerCredentialProvider;
import com.alibaba.sdk.android.oss.common.auth.OSSPlainTextAKSKCredentialProvider;
-import com.alibaba.sdk.android.oss.common.utils.BinaryUtil;
import com.alibaba.sdk.android.oss.common.utils.OSSUtils;
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.CreateBucketRequest;
import com.alibaba.sdk.android.oss.model.DeleteBucketRequest;
@@ -34,9 +32,9 @@
import com.alibaba.sdk.android.oss.model.ImagePersistResult;
import com.alibaba.sdk.android.oss.model.ListObjectsRequest;
import com.alibaba.sdk.android.oss.model.ListObjectsResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
import com.alibaba.sdk.android.oss.model.MultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.OSSRequest;
-import com.alibaba.sdk.android.oss.model.ObjectMetadata;
import com.alibaba.sdk.android.oss.model.PutObjectRequest;
import com.alibaba.sdk.android.oss.model.PutObjectResult;
import com.alibaba.sdk.android.oss.model.ResumableUploadRequest;
@@ -45,28 +43,15 @@
import com.alibaba.sdk.android.oss.model.TriggerCallbackResult;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import static com.alibaba.oss.app.Config.OSS_ACCESS_KEY_ID;
-import static com.alibaba.oss.app.Config.OSS_ACCESS_KEY_SECRET;
-
-import com.alibaba.oss.app.OSSWrapper;
-
/**
* Created by mOss on 2015/12/7 0007.
* 支持普通上传,普通下载
@@ -380,6 +365,49 @@ public void onFailure(ResumableUploadRequest request, ClientException clientExce
});
}
+ public void asyncResumableDownload(String downloadPath) {
+ Map header = new HashMap<>();
+ ResumableDownloadRequest request = new ResumableDownloadRequest(Config.BUCKET_NAME, "landscape-painting1.jpeg", downloadPath + "/landscape-painting.jpeg");
+ request.setEnableCheckPoint(true);
+// request.setRange(new Range(0, 1024 * 1024));
+ request.setPartSize(100 * 1024);
+ request.setRequestHeader(header);
+ request.setCheckPointFilePath(downloadPath);
+ request.setCRC64(OSSRequest.CRC64Config.YES);
+ request.setProgressListener(new OSSProgressCallback() {
+ @Override
+ public void onProgress(Object request, long currentSize, long totalSize) {
+ Log.i("MultipartDownload", "currentSize: " + currentSize + ", totalSize: " + totalSize);
+ if (totalSize == 0) {
+ return;
+ }
+ int progress = (int) (100 * currentSize / totalSize);
+ mDisplayer.updateProgress(progress);
+ mDisplayer.displayInfo("上传进度: " + String.valueOf(progress) + "%");
+ }
+ });
+ final long start = System.currentTimeMillis();
+ final OSSAsyncTask task = mOss.asyncResumableDownload(request, new OSSCompletedCallback() {
+ @Override
+ public void onSuccess(ResumableDownloadRequest request, ResumableDownloadResult result) {
+ Log.i("MultipartDownload", result.getMetadata().toString());
+ long time = System.currentTimeMillis() - start;
+ Log.i("time", time + "");
+ }
+
+ @Override
+ public void onFailure(ResumableDownloadRequest request, ClientException clientException, ServiceException serviceException) {
+ if (clientException != null) {
+ clientException.printStackTrace();
+ Log.i("clientException", "clientException");
+ } else if (serviceException != null) {
+ Log.i("serviceException", "serviceException");
+ serviceException.printStackTrace();
+ }
+ }
+ });
+ }
+
// If the bucket is private, the signed URL is required for the access.
// Expiration time is specified in the signed URL.
public void presignURLWithBucketAndKey(final String objectKey) {
diff --git a/app/src/main/java/com/alibaba/oss/app/view/MainActivity.java b/app/src/main/java/com/alibaba/oss/app/view/MainActivity.java
index 358af241..d2c3439c 100755
--- a/app/src/main/java/com/alibaba/oss/app/view/MainActivity.java
+++ b/app/src/main/java/com/alibaba/oss/app/view/MainActivity.java
@@ -34,8 +34,10 @@
import com.alibaba.sdk.android.oss.common.auth.OSSAuthCredentialsProvider;
import com.alibaba.sdk.android.oss.common.auth.OSSCredentialProvider;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -346,6 +348,15 @@ public void onClick(View v) {
}
});
+ final String filePath = getFilesDir() + "";
+ Button multipartDownloadButton = (Button) findViewById(R.id.resumableDownload);
+ multipartDownloadButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mService.asyncResumableDownload(filePath);
+ }
+ });
+
Button resumableButton = (Button) findViewById(R.id.resumableUpload);
resumableButton.setOnClickListener(new View.OnClickListener() {
@Override
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 3aa57092..c547d963 100755
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -260,7 +260,18 @@
android:layout_height="match_parent"
android:text="@string/resumableUpload" />
-
+
+
+
manage
ossSign
resumable
+ resumableDownload
multipart
cutomsign
batch
diff --git a/changelog.txt b/changelog.txt
index 450b2995..b237fb4e 100755
--- a/changelog.txt
+++ b/changelog.txt
@@ -17,6 +17,10 @@ Github地址:https://github.com/aliyun/aliyun-oss-android-sdk
更新日志:
+2021/08/18
+- release 2.9.9
+1. Delete the useless OSSSQLiteHelper class
+
2021/07/23
- release 2.9.8
1. Migrate from jcenter to mavenCentral
diff --git a/oss-android-sdk/build.gradle b/oss-android-sdk/build.gradle
index e07a63f0..b3943915 100644
--- a/oss-android-sdk/build.gradle
+++ b/oss-android-sdk/build.gradle
@@ -16,7 +16,7 @@ android {
minSdkVersion 14
targetSdkVersion 29
versionCode 40
- versionName "2.9.8"
+ versionName "2.9.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
diff --git a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/BaseTestCase.java b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/BaseTestCase.java
index 573fb39f..64e01deb 100644
--- a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/BaseTestCase.java
+++ b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/BaseTestCase.java
@@ -38,7 +38,9 @@ public abstract class BaseTestCase {
protected static OSS oss;
@Rule
- public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.ACCESS_NETWORK_STATE,
+ Manifest.permission.ACCESS_WIFI_STATE);
abstract void initTestData() throws Exception;
void initOSSClient() {
diff --git a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestConfig.java b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestConfig.java
index 20967cc2..01d64b80 100644
--- a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestConfig.java
+++ b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestConfig.java
@@ -40,6 +40,8 @@
import com.alibaba.sdk.android.oss.model.DeleteBucketResult;
import com.alibaba.sdk.android.oss.model.DeleteObjectRequest;
import com.alibaba.sdk.android.oss.model.DeleteObjectResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
import com.alibaba.sdk.android.oss.model.GetBucketACLRequest;
import com.alibaba.sdk.android.oss.model.GetBucketACLResult;
import com.alibaba.sdk.android.oss.model.GetBucketInfoRequest;
@@ -76,7 +78,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -866,4 +867,24 @@ public void onFailure(CompleteMultipartUploadRequest request, ClientException cl
this.serviceException = serviceException;
}
}
+
+ public final static class TestResumableDownloadCallback implements OSSCompletedCallback {
+
+ public ResumableDownloadRequest request;
+ public ResumableDownloadResult result;
+ public ClientException clientException;
+ public ServiceException serviceException;
+ @Override
+ public void onSuccess(ResumableDownloadRequest request, ResumableDownloadResult result) {
+ this.request = request;
+ this.result = result;
+ }
+
+ @Override
+ public void onFailure(ResumableDownloadRequest request, ClientException clientException, ServiceException serviceException) {
+ this.request = request;
+ this.clientException = clientException;
+ this.serviceException = serviceException;
+ }
+ }
}
diff --git a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestUtils.java b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestUtils.java
index 78aac976..1d0cfba2 100644
--- a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestUtils.java
+++ b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/OSSTestUtils.java
@@ -15,6 +15,7 @@
import com.alibaba.sdk.android.oss.model.ListObjectsResult;
import com.alibaba.sdk.android.oss.model.MultipartUpload;
import com.alibaba.sdk.android.oss.model.OSSObjectSummary;
+import com.alibaba.sdk.android.oss.model.Range;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -72,6 +73,16 @@ public static void checkFileMd5(OSS oss, String bucket, String objectKey, String
assertEquals(200, getRs.getStatusCode());
}
+ public static void checkFileMd5(OSS oss, String bucket, String objectKey, Range range, String filePath) throws IOException, NoSuchAlgorithmException, ClientException, ServiceException {
+ GetObjectRequest getRq = new GetObjectRequest(bucket, objectKey);
+ getRq.setRange(range);
+ GetObjectResult getRs = oss.getObject(getRq);
+ String localMd5 = BinaryUtil.calculateMd5Str(filePath);
+ String remoteMd5 = getMd5(getRs);
+ assertEquals(true, localMd5.equals(remoteMd5));
+ assertNotNull(getRs);
+ }
+
public static void checkFileMd5(OSS oss, String bucket, String objectKey, FileDescriptor fileDescriptor) throws IOException, NoSuchAlgorithmException, ClientException, ServiceException {
GetObjectRequest getRq = new GetObjectRequest(bucket, objectKey);
GetObjectResult getRs = oss.getObject(getRq);
diff --git a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableDownloadTest.java b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableDownloadTest.java
new file mode 100644
index 00000000..fd492cdd
--- /dev/null
+++ b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableDownloadTest.java
@@ -0,0 +1,292 @@
+package com.alibaba.sdk.android;
+
+import android.support.test.InstrumentationRegistry;
+
+import com.alibaba.sdk.android.oss.ClientException;
+import com.alibaba.sdk.android.oss.ServiceException;
+import com.alibaba.sdk.android.oss.callback.OSSProgressCallback;
+import com.alibaba.sdk.android.oss.common.OSSLog;
+import com.alibaba.sdk.android.oss.internal.OSSAsyncTask;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
+import com.alibaba.sdk.android.oss.model.OSSRequest;
+import com.alibaba.sdk.android.oss.model.PutObjectRequest;
+import com.alibaba.sdk.android.oss.model.Range;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class ResumableDownloadTest extends BaseTestCase {
+
+ private static final String RESUMABLE_DOWNLOAD_OBJECT_KEY = "multipartDownloadFile";
+ private static final String DOWNLOAD_PATH = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath() + "/file10m";
+ private static final String CHECKPOINT_PATH = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath();
+ private String file10mPath = OSSTestConfig.FILE_DIR + "file10m";
+
+ @Override
+ void initTestData() throws Exception {
+ OSSTestConfig.initLocalFile();
+ PutObjectRequest putObjectRequest = new PutObjectRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, file10mPath);
+ oss.putObject(putObjectRequest);
+ }
+
+ @Test
+ public void testResumableDownload() throws ClientException, ServiceException, IOException, NoSuchAlgorithmException {
+
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setProgressListener(new OSSProgressCallback() {
+ @Override
+ public void onProgress(Object request, long currentSize, long totalSize) {
+ OSSLog.logDebug("mul_download_progress: " + currentSize + " total_size: " + totalSize, false);
+ }
+ });
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ }
+
+ @Test
+ public void testResumableDownloadWithInvalidBucketName() {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest("mBucketName", RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ ClientException exception = callback.clientException;
+ assertNotNull(exception);
+ assertTrue(exception.getMessage().contains("The bucket name is invalid"));
+ }
+
+ @Test
+ public void testResumableDownloadWithInvalidObjectKey() {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, "//invalidObjectKey", DOWNLOAD_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ ClientException exception = callback.clientException;
+ assertNotNull(exception);
+ assertTrue(exception.getMessage().contains("The object key is invalid"));
+ }
+
+ @Test
+ public void testResumableDownloadWithNullObjectKey() {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, null, DOWNLOAD_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ ClientException exception = callback.clientException;
+ assertNotNull(exception);
+ assertTrue(exception.getMessage().contains("The object key is invalid"));
+ }
+
+ @Test
+ public void testGetNotExistObject() throws Exception {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, "objectKey", DOWNLOAD_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ ServiceException exception = callback.serviceException;
+ assertEquals(404, exception.getStatusCode());
+ }
+
+ @Test
+ public void testConcurrentResumableDownload() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ PutObjectRequest request = new PutObjectRequest(mBucketName, "ResumableDownload" + i, file10mPath);
+ oss.putObject(request);
+ }
+ final CountDownLatch latch = new CountDownLatch(5);
+ for (int i = 0; i < 5; i++) {
+ final int index = i;
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, "ResumableDownload" + index, DOWNLOAD_PATH + index);
+ oss.asyncResumableDownload(request, null).getResult();
+ latch.countDown();
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ latch.countDown();
+ }
+ }
+ }).start();
+ }
+ latch.await();
+ }
+
+ @Test
+ public void testResumableDownloadWithCancel() throws InterruptedException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+
+ task.cancel();
+ task.waitUntilFinished();
+ ClientException exception = callback.clientException;
+ assertTrue(exception.getMessage().contains("Resumable download cancel"));
+ }
+
+ @Test
+ public void testResumableDownloadWithCheckpoint() throws InterruptedException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ final int[] progress = {0};
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ request.setCRC64(OSSRequest.CRC64Config.YES);
+ final AtomicBoolean needCancelled = new AtomicBoolean(false);
+ request.setProgressListener(new OSSProgressCallback() {
+ @Override
+ public void onProgress(Object request, long currentSize, long totalSize) {
+ progress[0] = (int) ((float)currentSize / totalSize);
+ if (currentSize > totalSize / 2) {
+ needCancelled.set(true);
+ }
+ }
+ });
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+
+ while (!needCancelled.get()) {
+ Thread.sleep(100);
+ }
+ task.cancel();
+ task.waitUntilFinished();
+ assertNull(callback.result);
+ assertNotNull(callback.clientException);
+
+ Thread.sleep(1000l);
+
+ request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ request.setCRC64(OSSRequest.CRC64Config.YES);
+ request.setProgressListener(new OSSProgressCallback() {
+ @Override
+ public void onProgress(Object request, long currentSize, long totalSize) {
+ int p = (int) ((float)currentSize / totalSize);
+ assertTrue(p >= progress[0]);
+ }
+ });
+ task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+ }
+
+ @Test
+ public void testResumableDownloadFile() throws InterruptedException, ClientException, ServiceException, IOException, NoSuchAlgorithmException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+
+ Thread.sleep(100);
+ task.cancel();
+
+ PutObjectRequest putRequest = new PutObjectRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, OSSTestConfig.FILE_DIR + "file1m");
+ oss.putObject(putRequest);
+
+ callback = new OSSTestConfig.TestResumableDownloadCallback();
+ task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ }
+
+ @Test
+ public void testResumableDownloadSmallFile() throws ClientException, ServiceException, NoSuchAlgorithmException, IOException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ }
+
+ @Test
+ public void testResumableDownloadBigFile() throws ClientException, ServiceException, NoSuchAlgorithmException, IOException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ }
+
+ @Test
+ public void testSyncResumableDownload() throws ClientException, ServiceException, NoSuchAlgorithmException, IOException {
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ ResumableDownloadResult result = oss.syncResumableDownload(request);
+
+ assertEquals(result.getStatusCode(), 200);
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ }
+
+ @Test
+ public void testResumableErrorRange() throws ClientException, ServiceException, NoSuchAlgorithmException, IOException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setRange(new Range(-100, 0));
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ assertTrue(callback.clientException.getMessage().contains("Range is invalid"));
+
+ request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setRange(new Range(100, 0));
+ request.setEnableCheckPoint(true);
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ assertTrue(callback.clientException.getMessage().contains("Range is invalid"));
+ }
+
+ @Test
+ public void testResumableDownloadFileOfRange() throws ClientException, ServiceException, NoSuchAlgorithmException, IOException {
+ OSSTestConfig.TestResumableDownloadCallback callback = new OSSTestConfig.TestResumableDownloadCallback();
+
+ ResumableDownloadRequest request = new ResumableDownloadRequest(mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, DOWNLOAD_PATH);
+ request.setEnableCheckPoint(true);
+ request.setRange(new Range(100, -1));
+ request.setCheckPointFilePath(CHECKPOINT_PATH);
+ OSSAsyncTask task = oss.asyncResumableDownload(request, callback);
+ task.waitUntilFinished();
+
+ OSSTestUtils.checkFileMd5(oss, mBucketName, RESUMABLE_DOWNLOAD_OBJECT_KEY, new Range(100, -1), DOWNLOAD_PATH);
+ }
+}
diff --git a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableUploadTest.java b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableUploadTest.java
index 2277155f..10002e6e 100644
--- a/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableUploadTest.java
+++ b/oss-android-sdk/src/androidTest/java/com/alibaba/sdk/android/ResumableUploadTest.java
@@ -444,7 +444,7 @@ public void onProgress(ResumableUploadRequest request, long currentSize, long to
OSSTestUtils.checkFileMd5(oss, mBucketName, UPLOAD_FILE1M, OSSTestConfig.FILE_DIR + UPLOAD_FILE1M);
}
- @Test
+// @Test
public void testResumableUploadCancelledAndResume() throws Exception {
final String objectKey = UPLOAD_DEFAULT_FILE;
ResumableUploadRequest request = new ResumableUploadRequest(mBucketName, objectKey,
@@ -607,10 +607,10 @@ public void onProgress(ResumableUploadRequest request, long currentSize, long to
OSSTestUtils.checkFileMd5(oss, mBucketName, UPLOAD_FILE1M, OSSTestConfig.FILE_DIR + UPLOAD_FILE1M);
}
- @Test
+// @Test
public void testResumableUploadMore1000AndCancel() throws Exception {
- File recordDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/oss_record/");
+ File recordDir = new File(OSSTestConfig.FILE_DIR + "/oss_record/");
if (!recordDir.exists()) {
recordDir.mkdirs();
}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSS.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSS.java
index da9d9c44..666ff86c 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSS.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSS.java
@@ -883,4 +883,24 @@ public boolean doesObjectExist(String bucketName, String objectKey)
*/
public OSSAsyncTask asyncRestoreObject(RestoreObjectRequest request, OSSCompletedCallback completedCallback);
+ /**
+ * Asynchronously do a resumable download
+ *
+ * @param request
+ * A {@link ResumableDownloadRequest} instance that specifies the bucket
+ * name and object key.
+ * @param completedCallback
+ * A {@link OSSCompletedCallback} instance that specifies callback functions
+ * @return A {@link OSSAsyncTask} instance.
+ */
+ public OSSAsyncTask asyncResumableDownload(ResumableDownloadRequest request, OSSCompletedCallback completedCallback);
+
+ /**
+ * Synchronously do a resumable download
+ *
+ * @param request
+ * A {@link ResumableDownloadRequest} instance that specifies the bucket
+ * name and object key.
+ */
+ public ResumableDownloadResult syncResumableDownload(ResumableDownloadRequest request) throws ClientException, ServiceException;
}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSClient.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSClient.java
index 9d3af6fd..93631b27 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSClient.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSClient.java
@@ -16,6 +16,7 @@
import com.alibaba.sdk.android.oss.model.AbortMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.AppendObjectRequest;
import com.alibaba.sdk.android.oss.model.AppendObjectResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.CopyObjectRequest;
@@ -63,6 +64,7 @@
import com.alibaba.sdk.android.oss.model.ListObjectsResult;
import com.alibaba.sdk.android.oss.model.ListPartsRequest;
import com.alibaba.sdk.android.oss.model.ListPartsResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
import com.alibaba.sdk.android.oss.model.MultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.PutBucketLifecycleRequest;
import com.alibaba.sdk.android.oss.model.PutBucketLifecycleResult;
@@ -582,4 +584,14 @@ public RestoreObjectResult restoreObject(RestoreObjectRequest request) throws Cl
public OSSAsyncTask asyncRestoreObject(RestoreObjectRequest request, OSSCompletedCallback completedCallback) {
return mOss.asyncRestoreObject(request, completedCallback);
}
+
+ @Override
+ public OSSAsyncTask asyncResumableDownload(ResumableDownloadRequest request, OSSCompletedCallback completedCallback) {
+ return mOss.asyncResumableDownload(request, completedCallback);
+ }
+
+ @Override
+ public ResumableDownloadResult syncResumableDownload(ResumableDownloadRequest request) throws ClientException, ServiceException {
+ return mOss.syncResumableDownload(request);
+ }
}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSImpl.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSImpl.java
index 8ef9a161..3690be0f 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSImpl.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/OSSImpl.java
@@ -21,6 +21,7 @@
import com.alibaba.sdk.android.oss.model.AbortMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.AppendObjectRequest;
import com.alibaba.sdk.android.oss.model.AppendObjectResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.CopyObjectRequest;
@@ -68,6 +69,7 @@
import com.alibaba.sdk.android.oss.model.ListObjectsResult;
import com.alibaba.sdk.android.oss.model.ListPartsRequest;
import com.alibaba.sdk.android.oss.model.ListPartsResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
import com.alibaba.sdk.android.oss.model.MultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.PutBucketLifecycleRequest;
import com.alibaba.sdk.android.oss.model.PutBucketLifecycleResult;
@@ -623,4 +625,15 @@ public RestoreObjectResult restoreObject(RestoreObjectRequest request) throws Cl
public OSSAsyncTask asyncRestoreObject(RestoreObjectRequest request, OSSCompletedCallback completedCallback) {
return internalRequestOperation.restoreObject(request, completedCallback);
}
+
+ @Override
+ public OSSAsyncTask asyncResumableDownload(ResumableDownloadRequest request, OSSCompletedCallback completedCallback) {
+ return extensionRequestOperation.resumableDownload(request, completedCallback);
+ }
+
+ @Override
+ public ResumableDownloadResult syncResumableDownload(ResumableDownloadRequest request) throws ClientException, ServiceException {
+ return extensionRequestOperation.resumableDownload(request, null).getResult();
+ }
+
}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/ServiceException.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/ServiceException.java
index 120463b5..748974ad 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/ServiceException.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/ServiceException.java
@@ -34,6 +34,8 @@
*/
public class ServiceException extends Exception {
+ public static final String PARSE_RESPONSE_FAIL = "SDKParseResponseFail";
+
private static final long serialVersionUID = 430933593095358673L;
/**
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSConstants.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSConstants.java
index 53bc71c7..aa3efa69 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSConstants.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSConstants.java
@@ -5,7 +5,7 @@
*/
public final class OSSConstants {
- public static final String SDK_VERSION = "2.9.8";
+ public static final String SDK_VERSION = "2.9.9";
public static final String DEFAULT_OSS_ENDPOINT = "http://oss-cn-hangzhou.aliyuncs.com";
public static final String DEFAULT_CHARSET_NAME = "utf-8";
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSSQLiteHelper.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSSQLiteHelper.java
deleted file mode 100644
index fa84f902..00000000
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/common/OSSSQLiteHelper.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.alibaba.sdk.android.oss.common;
-
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Created by jingdan on 2017/12/5.
- */
-
-public class OSSSQLiteHelper extends SQLiteOpenHelper {
-
- public final static String TABLE_NAME_PART_INFO = "part_info";
- private final static String CREATE_TABLE_PART_INFO =
- "create table if not exists " + TABLE_NAME_PART_INFO + "("
- + "id INTEGER primary key,"
- + "upload_id VARCHAR(255),"
- + "num INTEGER,"
- + "crc64 INTEGER,"
- + "size INTEGER,"
- + "etag VARCHAR(255))";
-
-
- public OSSSQLiteHelper(Context context) {
- this(context, "oss_android_sdk.db", null, 1);
- }
-
- public OSSSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_PART_INFO);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- }
-}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ExtensionRequestOperation.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ExtensionRequestOperation.java
index a090954c..aea68778 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ExtensionRequestOperation.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ExtensionRequestOperation.java
@@ -11,8 +11,10 @@
import com.alibaba.sdk.android.oss.common.utils.BinaryUtil;
import com.alibaba.sdk.android.oss.common.utils.OSSUtils;
import com.alibaba.sdk.android.oss.model.AbortMultipartUploadRequest;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
import com.alibaba.sdk.android.oss.model.CompleteMultipartUploadResult;
import com.alibaba.sdk.android.oss.model.HeadObjectRequest;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
import com.alibaba.sdk.android.oss.model.MultipartUploadRequest;
import com.alibaba.sdk.android.oss.model.OSSRequest;
import com.alibaba.sdk.android.oss.model.ResumableUploadRequest;
@@ -146,6 +148,13 @@ public OSSAsyncTask multipartUpload(MultipartUplo
, request, completedCallback, executionContext)), executionContext);
}
+ public OSSAsyncTask resumableDownload(ResumableDownloadRequest request,
+ OSSCompletedCallback completedCallback) {
+ ExecutionContext executionContext =
+ new ExecutionContext(apiOperation.getInnerClient(), request, apiOperation.getApplicationContext());
+ return OSSAsyncTask.wrapRequestTask(executorService.submit(new ResumableDownloadTask(apiOperation, request, completedCallback, executionContext)), executionContext);
+ }
+
private void setCRC64(OSSRequest request) {
Enum crc64 = request.getCRC64() != OSSRequest.CRC64Config.NULL ? request.getCRC64() :
(apiOperation.getConf().isCheckCRC64() ? OSSRequest.CRC64Config.YES : OSSRequest.CRC64Config.NO);
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResponseParsers.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResponseParsers.java
index 9122dd41..c893c3d4 100644
--- a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResponseParsers.java
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResponseParsers.java
@@ -801,8 +801,7 @@ public static ObjectMetadata parseObjectMetadata(Map headers)
}
}
- public static ServiceException parseResponseErrorXML(ResponseMessage response, boolean isHeadRequest)
- throws ClientException {
+ public static Exception parseResponseErrorXML(ResponseMessage response, boolean isHeadRequest) {
int statusCode = response.getStatusCode();
String requestId = response.getResponse().header(OSSHeaders.OSS_HEADER_REQUEST_ID);
@@ -843,11 +842,10 @@ public static ServiceException parseResponseErrorXML(ResponseMessage response, b
eventType = parser.next();
}
}
-
} catch (IOException e) {
- throw new ClientException(e);
+ return new ClientException(e.getMessage(), e);
} catch (XmlPullParserException e) {
- throw new ClientException(e);
+ return new ClientException(e.getMessage(), e);
}
}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResumableDownloadTask.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResumableDownloadTask.java
new file mode 100644
index 00000000..871cd03e
--- /dev/null
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/internal/ResumableDownloadTask.java
@@ -0,0 +1,682 @@
+package com.alibaba.sdk.android.oss.internal;
+
+import android.util.Log;
+
+import com.alibaba.sdk.android.oss.ClientException;
+import com.alibaba.sdk.android.oss.ServiceException;
+import com.alibaba.sdk.android.oss.TaskCancelException;
+import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback;
+import com.alibaba.sdk.android.oss.callback.OSSProgressCallback;
+import com.alibaba.sdk.android.oss.common.OSSLog;
+import com.alibaba.sdk.android.oss.common.utils.BinaryUtil;
+import com.alibaba.sdk.android.oss.common.utils.CRC64;
+import com.alibaba.sdk.android.oss.common.utils.OSSUtils;
+import com.alibaba.sdk.android.oss.exception.InconsistentException;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadResult;
+import com.alibaba.sdk.android.oss.model.GetObjectRequest;
+import com.alibaba.sdk.android.oss.model.GetObjectResult;
+import com.alibaba.sdk.android.oss.model.HeadObjectRequest;
+import com.alibaba.sdk.android.oss.model.HeadObjectResult;
+import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest;
+import com.alibaba.sdk.android.oss.model.OSSRequest;
+import com.alibaba.sdk.android.oss.model.OSSResult;
+import com.alibaba.sdk.android.oss.model.ObjectMetadata;
+import com.alibaba.sdk.android.oss.model.Range;
+import com.alibaba.sdk.android.oss.network.ExecutionContext;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.CheckedInputStream;
+
+public class ResumableDownloadTask implements Callable {
+ protected final int CPU_SIZE = Runtime.getRuntime().availableProcessors() * 2;
+ protected final int MAX_CORE_POOL_SIZE = CPU_SIZE < 5 ? CPU_SIZE : 5;
+ protected final int MAX_IMUM_POOL_SIZE = CPU_SIZE;
+ protected final int KEEP_ALIVE_TIME = 3000;
+ protected final int MAX_QUEUE_SIZE = 5000;
+ protected ThreadPoolExecutor mPoolExecutor =
+ new ThreadPoolExecutor(MAX_CORE_POOL_SIZE, MAX_IMUM_POOL_SIZE, KEEP_ALIVE_TIME,
+ TimeUnit.MILLISECONDS, new ArrayBlockingQueue(MAX_QUEUE_SIZE), new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable runnable) {
+ return new Thread(runnable, "oss-android-multipart-thread");
+ }
+ });
+ private ResumableDownloadRequest mRequest;
+ private InternalRequestOperation mOperation;
+ private OSSCompletedCallback mCompletedCallback;
+ private ExecutionContext mContext;
+ private OSSProgressCallback mProgressCallback;
+ private CheckPoint mCheckPoint;
+ protected Object mLock = new Object();
+ protected Exception mDownloadException;
+ protected long completedPartSize;
+ protected long downloadPartSize;
+ protected long mPartExceptionCount;
+ protected String checkpointPath;
+
+ ResumableDownloadTask(InternalRequestOperation operation,
+ ResumableDownloadRequest request,
+ OSSCompletedCallback completedCallback,
+ ExecutionContext context) {
+ this.mRequest = request;
+ this.mOperation = operation;
+ this.mCompletedCallback = completedCallback;
+ this.mContext = context;
+ this.mProgressCallback = request.getProgressListener();
+ }
+
+
+ @Override
+ public Result call() throws Exception {
+ try {
+ checkInitData();
+ ResumableDownloadResult result = doMultipartDownload();
+ if (mCompletedCallback != null) {
+ mCompletedCallback.onSuccess(mRequest, result);
+ }
+ return (Result) result;
+ } catch (ServiceException e) {
+ if (mCompletedCallback != null) {
+ mCompletedCallback.onFailure(mRequest, null, e);
+ }
+ throw e;
+ } catch (Exception e) {
+ ClientException temp;
+ if (e instanceof ClientException) {
+ temp = (ClientException) e;
+ } else {
+ temp = new ClientException(e.toString(), e);
+ }
+ if (mCompletedCallback != null) {
+ mCompletedCallback.onFailure(mRequest, temp, null);
+ }
+ throw temp;
+ }
+ }
+
+ protected void checkInitData() throws ClientException, ServiceException, IOException {
+
+ if (mRequest.getRange() != null && !mRequest.getRange().checkIsValid()) {
+ throw new ClientException("Range is invalid");
+ };
+ String recordFileName = BinaryUtil.calculateMd5Str((mRequest.getBucketName() + mRequest.getObjectKey()
+ + String.valueOf(mRequest.getPartSize()) + (mRequest.getCRC64() == OSSRequest.CRC64Config.YES ? "-crc64" : "")).getBytes());
+ checkpointPath = mRequest.getCheckPointFilePath() + File.separator + recordFileName;
+
+ mCheckPoint = new CheckPoint();
+
+ if (mRequest.getEnableCheckPoint()) {
+ try {
+ mCheckPoint.load(checkpointPath);
+ } catch (Exception e) {
+ removeFile(checkpointPath);
+ removeFile(mRequest.getTempFilePath());
+ }
+ if (!mCheckPoint.isValid(mOperation)) {
+ removeFile(checkpointPath);
+ removeFile(mRequest.getTempFilePath());
+
+ initCheckPoint();
+ }
+ } else {
+ initCheckPoint();
+ }
+ }
+
+ protected boolean removeFile(String filePath) {
+ boolean flag = false;
+ File file = new File(filePath);
+
+ if (file.isFile() && file.exists()) {
+ flag = file.delete();
+ }
+
+ return flag;
+ }
+
+ private void initCheckPoint() throws ClientException, ServiceException, IOException {
+ FileStat fileStat = FileStat.getFileStat(mOperation, mRequest.getBucketName(), mRequest.getObjectKey());
+ Range range = correctRange(mRequest.getRange(), fileStat.fileLength);
+ long downloadSize = range.getEnd() - range.getBegin();
+ createFile(mRequest.getTempFilePath(), downloadSize);
+
+ mCheckPoint.bucketName = mRequest.getBucketName();
+ mCheckPoint.objectKey = mRequest.getObjectKey();
+ mCheckPoint.fileStat = fileStat;
+ mCheckPoint.parts = splitFile(range, mCheckPoint.fileStat.fileLength, mRequest.getPartSize());
+ }
+
+ protected ResumableDownloadResult doMultipartDownload() throws ClientException, ServiceException, IOException, InterruptedException {
+ checkCancel();
+ ResumableDownloadResult resumableDownloadResult = new ResumableDownloadResult();
+
+ final DownloadFileResult result = new DownloadFileResult();
+ result.partResults = new ArrayList();
+
+ for (final DownloadPart part : mCheckPoint.parts) {
+ checkException();
+ if (mPoolExecutor != null && !part.isCompleted) {
+ mPoolExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ downloadPart(result, part);
+ Log.i("partResults", "start: " + part.start + ", end: " + part.end);
+ }
+ });
+ } else {
+ DownloadPartResult partResult = new DownloadPartResult();
+ partResult.partNumber = part.partNumber;
+ partResult.requestId = mCheckPoint.fileStat.requestId;
+ partResult.length = part.length;
+ if (mRequest.getCRC64() == OSSRequest.CRC64Config.YES) {
+ partResult.clientCRC = part.crc;
+ }
+ result.partResults.add(partResult);
+ downloadPartSize += 1;
+ completedPartSize += 1;
+ }
+ }
+ // Wait for all tasks to be completed
+ if (checkWaitCondition(mCheckPoint.parts.size())) {
+ synchronized (mLock) {
+ mLock.wait();
+ }
+ }
+ checkException();
+ Collections.sort(result.partResults, new Comparator() {
+ @Override
+ public int compare(DownloadPartResult downloadPartResult, DownloadPartResult t1) {
+ return downloadPartResult.partNumber - t1.partNumber;
+ }
+ });
+ if (mRequest.getCRC64() == OSSRequest.CRC64Config.YES && mRequest.getRange() == null) {
+ Long clientCRC = calcObjectCRCFromParts(result.partResults);
+ resumableDownloadResult.setClientCRC(clientCRC);
+ try {
+ OSSUtils.checkChecksum(clientCRC, mCheckPoint.fileStat.serverCRC, result.partResults.get(0).requestId);
+ } catch (InconsistentException e) {
+ removeFile(checkpointPath);
+ removeFile(mRequest.getTempFilePath());
+ throw e;
+ }
+ }
+ removeFile(checkpointPath);
+
+ File fromFile = new File(mRequest.getTempFilePath());
+ File toFile = new File(mRequest.getDownloadToFilePath());
+ moveFile(fromFile, toFile);
+
+ resumableDownloadResult.setServerCRC(mCheckPoint.fileStat.serverCRC);
+ resumableDownloadResult.setMetadata(result.metadata);
+ resumableDownloadResult.setRequestId(result.partResults.get(0).requestId);
+ resumableDownloadResult.setStatusCode(200);
+
+ return resumableDownloadResult;
+ }
+
+ private static Long calcObjectCRCFromParts(List partResults) {
+ long crc = 0;
+
+ for (DownloadPartResult partResult : partResults) {
+ if (partResult.clientCRC == null || partResult.length <= 0) {
+ return null;
+ }
+ crc = CRC64.combine(crc, partResult.clientCRC, partResult.length);
+ }
+ return new Long(crc);
+ }
+
+ private ArrayList splitFile(Range range, long fileSize, long partSize) {
+
+ if (fileSize <= 0) {
+ DownloadPart part = new DownloadPart();
+ part.start = 0;
+ part.end = -1;
+ part.length = 0;
+ part.partNumber = 0;
+
+ ArrayList parts = new ArrayList();
+ parts.add(part);
+ return parts;
+ }
+ long start = range.getBegin();
+ long size = range.getEnd() - range.getBegin();
+
+ long count = size / partSize;
+ if (size % partSize > 0) {
+ count += 1;
+ }
+
+ ArrayList parts = new ArrayList();
+ for (int i = 0; i < count; i++) {
+ DownloadPart part = new DownloadPart();
+ part.start = start + partSize * i;
+ part.end = start + partSize * (i + 1) - 1;
+ part.length = part.end - part.start + 1;
+ if (part.end >= start + size) {
+ part.end = -1;
+ part.length = start + size - part.start;
+ }
+ part.partNumber = i;
+ part.fileStart = i * partSize;
+ parts.add(part);
+ }
+ return parts;
+ }
+
+ private Range correctRange(Range range, long totalSize) {
+ long start = 0;
+ long size = totalSize;
+ if (range != null) {
+ start = range.getBegin();
+ if (range.getBegin() == -1) {
+ start = 0;
+ }
+ size = range.getEnd() - range.getBegin();
+ if (range.getEnd() == -1) {
+ size = totalSize - start;
+ }
+ }
+ return new Range(start, start + size);
+ }
+
+ private void downloadPart(DownloadFileResult downloadResult, DownloadPart part) {
+
+ RandomAccessFile output = null;
+ InputStream content = null;
+ try {
+
+ if (mContext.getCancellationHandler().isCancelled()) {
+ mPoolExecutor.getQueue().clear();
+ }
+
+ downloadPartSize += 1;
+
+ output = new RandomAccessFile(mRequest.getTempFilePath(), "rw");
+ output.seek(part.fileStart);
+
+ Map requestHeader = mRequest.getRequestHeader();
+
+ GetObjectRequest request = new GetObjectRequest(mRequest.getBucketName(), mRequest.getObjectKey());
+ request.setRange(new Range(part.start, part.end));
+ request.setRequestHeaders(requestHeader);
+ GetObjectResult result = mOperation.getObject(request, null).getResult();
+
+ content = result.getObjectContent();
+
+ byte[] buffer = new byte[(int)(part.length)];
+ long readLength = 0;
+ if (mRequest.getCRC64() == OSSRequest.CRC64Config.YES) {
+ content = new CheckedInputStream(content, new CRC64());
+ }
+
+ while ((readLength = content.read(buffer)) != -1) {
+ output.write(buffer, 0, (int) readLength);
+ }
+
+ synchronized (mLock) {
+
+ DownloadPartResult partResult = new DownloadPartResult();
+ partResult.partNumber = part.partNumber;
+ partResult.requestId = result.getRequestId();
+ partResult.length = result.getContentLength();
+ if (mRequest.getCRC64() == OSSRequest.CRC64Config.YES) {
+ Long clientCRC = ((CheckedInputStream)content).getChecksum().getValue();
+ partResult.clientCRC = clientCRC;
+
+ part.crc = clientCRC;
+ }
+ downloadResult.partResults.add(partResult);
+ if (downloadResult.metadata == null) {
+ downloadResult.metadata = result.getMetadata();
+ }
+
+ completedPartSize += 1;
+
+ if (mContext.getCancellationHandler().isCancelled()) {
+ // Cancel after the last task is completed
+ if (downloadPartSize == completedPartSize - mPartExceptionCount) {
+ checkCancel();
+ }
+ } else {
+ // After all tasks are completed, wake up the thread where the doMultipartDownload method is located
+ if (mCheckPoint.parts.size() == (completedPartSize - mPartExceptionCount)) {
+ notifyMultipartThread();
+ }
+ mCheckPoint.update(part.partNumber, true);
+ if (mRequest.getEnableCheckPoint()) {
+ mCheckPoint.dump(checkpointPath);
+ }
+ Range range = correctRange(mRequest.getRange(), mCheckPoint.fileStat.fileLength);
+ if (mProgressCallback != null) {
+ mProgressCallback.onProgress(mRequest, mCheckPoint.downloadLength, range.getEnd() - range.getBegin());
+ }
+ }
+ }
+ } catch (Exception e) {
+ processException(e);
+ } finally {
+ try {
+ if (output != null) {
+ output.close();
+ }
+ if (content != null) {
+ content.close();
+ }
+ } catch (IOException e) {
+ OSSLog.logThrowable2Local(e);
+ }
+ }
+ }
+
+ private void createFile(String filePath, long length) throws IOException {
+ File file = new File(filePath);
+ RandomAccessFile accessFile = null;
+
+ try {
+ accessFile = new RandomAccessFile(file, "rw");
+ accessFile.setLength(length);
+ } finally {
+ if (accessFile != null) {
+ accessFile.close();
+ }
+ }
+ }
+
+ private void moveFile(File fromFile, File toFile) throws IOException {
+
+ boolean rename = fromFile.renameTo(toFile);
+ if (!rename) {
+ Log.i("moveFile", "rename");
+ InputStream ist = null;
+ OutputStream ost = null;
+ try {
+ ist = new FileInputStream(fromFile);
+ ost = new FileOutputStream(toFile);
+ copyFile(ist, ost);
+ if (!fromFile.delete()) {
+ throw new IOException("Failed to delete original file '" + fromFile + "'");
+ }
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (ist != null) {
+ ist.close();
+ }
+ if (ost != null) {
+ ost.close();
+ }
+ }
+ }
+ }
+
+ private void copyFile(InputStream ist, OutputStream ost) throws IOException {
+ byte[] buffer = new byte[4096];
+ int byteCount;
+ while ((byteCount = ist.read(buffer)) != -1) {
+ ost.write(buffer, 0, byteCount);
+ }
+ }
+
+ protected void notifyMultipartThread() {
+ mLock.notify();
+ mPartExceptionCount = 0;
+ }
+
+ protected void processException(Exception e) {
+ synchronized (mLock) {
+ mPartExceptionCount++;
+ if (mDownloadException == null) {
+ mDownloadException = e;
+ mLock.notify();
+ }
+ }
+ }
+
+ protected void releasePool() {
+ if (mPoolExecutor != null) {
+ mPoolExecutor.getQueue().clear();
+ mPoolExecutor.shutdown();
+ }
+ }
+
+ protected void checkException() throws IOException, ServiceException, ClientException {
+ if (mDownloadException != null) {
+ releasePool();
+ if (mDownloadException instanceof IOException) {
+ throw (IOException) mDownloadException;
+ } else if (mDownloadException instanceof ServiceException) {
+ throw (ServiceException) mDownloadException;
+ } else if (mDownloadException instanceof ClientException) {
+ throw (ClientException) mDownloadException;
+ } else {
+ ClientException clientException =
+ new ClientException(mDownloadException.getMessage(), mDownloadException);
+ throw clientException;
+ }
+ }
+ }
+
+ protected boolean checkWaitCondition(int partNum) {
+ if (completedPartSize == partNum) {
+ return false;
+ }
+ return true;
+ }
+
+ protected void checkCancel() throws ClientException {
+ if (mContext.getCancellationHandler().isCancelled()) {
+ TaskCancelException e = new TaskCancelException("Resumable download cancel");
+ throw new ClientException(e.getMessage(), e, true);
+ }
+ }
+
+ protected Boolean checkFile() throws IOException {
+ String filePath = mRequest.getTempFilePath();
+ String md5 = BinaryUtil.calculateBase64Md5(filePath);
+ return md5.equals(mCheckPoint.fileStat.md5);
+ }
+
+ static class DownloadPart implements Serializable {
+ private static final long serialVersionUID = -3506020776131733942L;
+
+ public int partNumber;
+ public long start; // start index;
+ public long end; // end index;
+ public boolean isCompleted; // flag of part download finished or not;
+ public long length; // length of part
+ public long fileStart; // start index of file
+ public long crc; // part crc.
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + partNumber;
+ result = prime * result + (isCompleted ? 1231 : 1237);
+ result = prime * result + (int) (end ^ (end >>> 32));
+ result = prime * result + (int) (start ^ (start >>> 32));
+ result = prime * result + (int) (crc ^ (crc >>> 32));
+ return result;
+ }
+ }
+
+ static class CheckPoint implements Serializable {
+
+ private static final long serialVersionUID = -8470273912385636504L;
+
+ public int md5;
+ public String downloadFile;
+ public String bucketName;
+ public String objectKey;
+ public FileStat fileStat;
+ public ArrayList parts;
+ public long downloadLength;
+
+ /**
+ * Loads the checkpoint data from the checkpoint file.
+ */
+ public synchronized void load(String cpFile) throws IOException, ClassNotFoundException {
+ FileInputStream fileIn = null;
+ ObjectInputStream in = null;
+ try {
+ fileIn = new FileInputStream(cpFile);
+ in = new ObjectInputStream(fileIn);
+ CheckPoint dcp = (CheckPoint) in.readObject();
+ assign(dcp);
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ if (fileIn != null) {
+ fileIn.close();
+ }
+ }
+ }
+
+ /**
+ * Writes the checkpoint data to the checkpoint file.
+ */
+ public synchronized void dump(String cpFile) throws IOException {
+ this.md5 = hashCode();
+ FileOutputStream fileOut = null;
+ ObjectOutputStream outStream = null;
+ try {
+ fileOut = new FileOutputStream(cpFile);
+ outStream = new ObjectOutputStream(fileOut);
+ outStream.writeObject(this);
+ } finally {
+ if (outStream != null) {
+ outStream.close();
+ }
+ if (fileOut != null) {
+ fileOut.close();
+ }
+ }
+ }
+
+ /**
+ * Updates the part's download status.
+ *
+ * @throws IOException
+ */
+ public synchronized void update(int index, boolean completed) throws IOException {
+ parts.get(index).isCompleted = completed;
+ downloadLength += parts.get(index).length;
+ }
+
+ /**
+ * Check if the object matches the checkpoint information.
+ */
+ public synchronized boolean isValid(InternalRequestOperation operation) throws ClientException, ServiceException {
+ // Compare magic and md5 of checkpoint
+ if (this.md5 != hashCode()) {
+ return false;
+ }
+
+ FileStat fileStat = FileStat.getFileStat(operation, bucketName, objectKey);
+
+ // Object's size, last modified time or ETAG are not same as the one
+ // in the checkpoint.
+ if (this.fileStat.fileLength != fileStat.fileLength || !this.fileStat.md5.equals(fileStat.md5)
+ || !this.fileStat.etag.equals(fileStat.etag)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((bucketName == null) ? 0 : bucketName.hashCode());
+ result = prime * result + ((downloadFile == null) ? 0 : downloadFile.hashCode());
+ result = prime * result + ((objectKey == null) ? 0 : objectKey.hashCode());
+ result = prime * result + ((fileStat == null) ? 0 : fileStat.hashCode());
+ result = prime * result + ((parts == null) ? 0 : parts.hashCode());
+ result = prime * result + (int) (downloadLength ^ (downloadLength >>> 32));
+ return result;
+ }
+
+ private void assign(CheckPoint dcp) {
+ this.md5 = dcp.md5;
+ this.downloadFile = dcp.downloadFile;
+ this.bucketName = dcp.bucketName;
+ this.objectKey = dcp.objectKey;
+ this.fileStat = dcp.fileStat;
+ this.parts = dcp.parts;
+ this.downloadLength = dcp.downloadLength;
+ }
+ }
+
+ static class FileStat implements Serializable {
+
+ private static final long serialVersionUID = 3896323364904643963L;
+
+ public long fileLength;
+ public String md5;
+ public String etag;
+ public Long serverCRC;
+ public String requestId;
+
+ public static FileStat getFileStat(InternalRequestOperation operation, String bucketName, String objectKey) throws ClientException, ServiceException {
+ HeadObjectRequest request = new HeadObjectRequest(bucketName, objectKey);
+ HeadObjectResult result = operation.headObject(request, null).getResult();
+
+ FileStat fileStat = new FileStat();
+ fileStat.fileLength = result.getMetadata().getContentLength();
+ fileStat.etag = result.getMetadata().getETag();
+ fileStat.md5 = result.getMetadata().getContentMD5();
+ fileStat.serverCRC = result.getServerCRC();
+ fileStat.requestId = result.getRequestId();
+
+ return fileStat;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((etag == null) ? 0 : etag.hashCode());
+ result = prime * result + ((md5 == null) ? 0 : md5.hashCode());
+ result = prime * result + (int) (fileLength ^ (fileLength >>> 32));
+ return result;
+ }
+ }
+
+ static class DownloadPartResult {
+
+ public int partNumber;
+ public String requestId;
+ public Long clientCRC;
+ public long length;
+ }
+
+ class DownloadFileResult extends OSSResult {
+
+ public ArrayList partResults;
+ public ObjectMetadata metadata;
+ }
+}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadRequest.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadRequest.java
new file mode 100644
index 00000000..57c73b6d
--- /dev/null
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadRequest.java
@@ -0,0 +1,174 @@
+package com.alibaba.sdk.android.oss.model;
+
+import com.alibaba.sdk.android.oss.callback.OSSProgressCallback;
+
+import java.util.Map;
+
+public class ResumableDownloadRequest extends OSSRequest {
+
+ // Object bucket's name
+ private String bucketName;
+
+ // Object Key
+ private String objectKey;
+
+ // Gets the range of the object to return (starting from 0 to the object length -1)
+ private Range range;
+
+ // progress callback run with not ui thread
+ private OSSProgressCallback progressListener;
+
+ //
+ private String downloadToFilePath;
+
+ private Boolean enableCheckPoint = false;
+ private String checkPointFilePath;
+
+ private long partSize = 256 * 1024;
+
+ private Map requestHeader;
+
+ /**
+ * Constructor
+ *
+ * @param bucketName The target object's bucket name
+ * @param objectKey The target object's key
+ * @param downloadToFilePath The local path of the file to download
+ */
+ public ResumableDownloadRequest(String bucketName, String objectKey, String downloadToFilePath) {
+ this.bucketName = bucketName;
+ this.objectKey = objectKey;
+ this.downloadToFilePath = downloadToFilePath;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param bucketName The target object's bucket name
+ * @param objectKey The target object's key
+ * @param downloadToFilePath The local path of the file to download
+ * @param checkPointFilePath The checkpoint files' directory
+ */
+ public ResumableDownloadRequest(String bucketName, String objectKey, String downloadToFilePath, String checkPointFilePath) {
+ this.bucketName = bucketName;
+ this.objectKey = objectKey;
+ this.downloadToFilePath = downloadToFilePath;
+ this.enableCheckPoint = true;
+ this.checkPointFilePath = checkPointFilePath;
+ }
+
+ public String getBucketName() {
+ return bucketName;
+ }
+
+ /**
+ * Sets the OSS bucket name
+ *
+ * @param bucketName
+ */
+ public void setBucketName(String bucketName) {
+ this.bucketName = bucketName;
+ }
+
+ public String getObjectKey() {
+ return objectKey;
+ }
+
+ /**
+ * Sets the OSS object key
+ *
+ * @param objectKey
+ */
+ public void setObjectKey(String objectKey) {
+ this.objectKey = objectKey;
+ }
+
+ public Range getRange() {
+ return range;
+ }
+
+ /**
+ * Sets the range to download
+ *
+ * @param range The range to download (starting from 0 to the length -1)
+ */
+ public void setRange(Range range) {
+ this.range = range;
+ }
+
+ public OSSProgressCallback getProgressListener() {
+ return progressListener;
+ }
+
+ /**
+ * Sets the upload progress callback
+ */
+ public void setProgressListener(OSSProgressCallback progressListener) {
+ this.progressListener = progressListener;
+ }
+
+ public String getDownloadToFilePath() {
+ return downloadToFilePath;
+ }
+
+ /**
+ * Sets the local path of the file to download
+ *
+ * @param downloadToFilePath the local path of the file to upload
+ */
+ public void setDownloadToFilePath(String downloadToFilePath) {
+ this.downloadToFilePath = downloadToFilePath;
+ }
+
+ public Boolean getEnableCheckPoint() {
+ return enableCheckPoint;
+ }
+
+
+ public void setEnableCheckPoint(Boolean enableCheckPoint) {
+ this.enableCheckPoint = enableCheckPoint;
+ }
+
+ public String getCheckPointFilePath() {
+ return checkPointFilePath;
+ }
+
+ /**
+ * Sets the checkpoint files' directory (the directory must exist and is absolute directory path)
+ *
+ * @param checkPointFilePath the checkpoint files' directory
+ */
+ public void setCheckPointFilePath(String checkPointFilePath) {
+ this.checkPointFilePath = checkPointFilePath;
+ }
+
+ public long getPartSize() {
+ return partSize;
+ }
+
+ /**
+ * Sets the part size, by default it's 256KB and the minimal value is 100KB
+ *
+ * @param partSize size in byte
+ */
+ public void setPartSize(long partSize) {
+ this.partSize = partSize;
+ }
+
+ public String getTempFilePath() {
+ return downloadToFilePath + ".tmp";
+ }
+
+ public Map getRequestHeader() {
+ return requestHeader;
+ }
+
+ /**
+ * Sets the request headers
+ *
+ * @param requestHeader
+ */
+ public void setRequestHeader(Map requestHeader) {
+ this.requestHeader = requestHeader;
+ }
+}
diff --git a/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadResult.java b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadResult.java
new file mode 100644
index 00000000..3ff893d2
--- /dev/null
+++ b/oss-android-sdk/src/main/java/com/alibaba/sdk/android/oss/model/ResumableDownloadResult.java
@@ -0,0 +1,21 @@
+package com.alibaba.sdk.android.oss.model;
+
+import java.util.ArrayList;
+
+public class ResumableDownloadResult extends OSSResult {
+
+ private ObjectMetadata metadata;
+
+ /**
+ * Gets the metadata
+ *
+ * @return object metadata
+ */
+ public ObjectMetadata getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(ObjectMetadata metadata) {
+ this.metadata = metadata;
+ }
+}
diff --git a/project.properties b/project.properties
index 3bb24f3f..75ebc49c 100644
--- a/project.properties
+++ b/project.properties
@@ -2,7 +2,7 @@
project.name=aliyun-oss-sdk-android
project.groupId=com.aliyun.dpa
project.artifactId=oss-android-sdk
-project.version=2.9.8
+project.version=2.9.9
project.packaging=aar
project.siteUrl=https://github.com/aliyun/aliyun-oss-android-sdk
project.gitUrl=https://github.com/aliyun/aliyun-oss-android-sdk.git