diff --git a/.travis.yml b/.travis.yml
index 1ac57d0ff..0d056c141 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,4 +15,4 @@ before_script:
- ./ci/wait_for_emulator.sh
- adb shell input keyevent 82 &
-script: ./gradlew connectedInstrumentTest
+script: ./gradlew connectedInstrumentTest --info
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a886312aa..3e13ddb54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
## CHANGE LOG
+### v6.1.0
+2014-09-02 issue [#67](https://github.com/qiniu/android-sdk/pull/67)
+
+- [#64] 1.中断上传;2.uri不能直接转换为File时,转换为InputStream处理; 3.重新分片上传,可设置断点记录;4.重构InputStreamAt,MultipartEntity,CallBack等;
+
+
### v6.0.5
2014-07-20 issue [#61](https://github.com/qiniu/android-sdk/pull/61)
diff --git a/res/layout/resumable.xml b/res/layout/resumable.xml
index f60ea3fde..1746b698f 100644
--- a/res/layout/resumable.xml
+++ b/res/layout/resumable.xml
@@ -38,7 +38,7 @@
implements ICancel {
- HttpRequestBase mHttpRequest;
- CallRet mRet;
- boolean failed;
- public void setup(HttpRequestBase httpRequest, CallRet ret) {
- mHttpRequest = httpRequest;
- mRet = ret;
- }
- public void upload(long current, long total) {
- publishProgress(current, total);
- }
-
- @Override
- protected Object doInBackground(Object... objects) {
- try {
- HttpResponse resp = roundtrip(mHttpRequest);
- int statusCode = resp.getStatusLine().getStatusCode();
- String phrase = resp.getStatusLine().getReasonPhrase();
-
- Header h = resp.getFirstHeader("X-Log");
- String xl = h == null ? "":h.getValue();
-
- h = resp.getFirstHeader("X-Reqid");
- String reqId = h == null ? "":h.getValue();
-
- if (statusCode == 401) {
- return new QiniuException(401, reqId, phrase); // android 2.3 will not response
- }
-
- byte[] data = EntityUtils.toByteArray(resp.getEntity());
- if (statusCode / 100 == 2) {
- return data;
- }
-
- if (data.length > 0) {
- return new QiniuException(statusCode, reqId, new String(data));
- }
- if (xl.length() > 0) {
- return new QiniuException(statusCode, reqId, xl);
- }
- return new QiniuException(statusCode, reqId, phrase);
- } catch (IOException e) {
- e.printStackTrace();
- return new QiniuException(QiniuException.IO, "net IOException", e);
- }
- }
-
- @Override
- protected void onProgressUpdate(Object... values) {
- if (failed){
- return;
- }
- if (values.length == 1 && values[0] instanceof QiniuException) {
- mRet.onFailure((QiniuException) values[0]);
- failed = true;
- return;
- }
- mRet.onProcess((Long) values[0], (Long) values[1]);
- }
-
- @Override
- protected void onPostExecute(Object o) {
- if (failed) {
- return;
- }
- if (o instanceof QiniuException) {
- mRet.onFailure((QiniuException) o);
- return;
- }
- mRet.onSuccess((byte[]) o);
- }
-
- public void onFailure(QiniuException ex) {
- publishProgress(ex);
- cancel(true);
- }
- };
-
- public static Client defaultClient() {
- return new Client(getMultithreadClient());
- }
-
- public static HttpClient getMultithreadClient() {
- HttpClient client = new DefaultHttpClient();
- ClientConnectionManager mgr = client.getConnectionManager();
- HttpParams params = client.getParams();
- client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, mgr.getSchemeRegistry()), params);
- return client;
- }
-}
diff --git a/src/com/qiniu/auth/JSONObjectRet.java b/src/com/qiniu/auth/JSONObjectRet.java
deleted file mode 100644
index 7ffa10570..000000000
--- a/src/com/qiniu/auth/JSONObjectRet.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.qiniu.auth;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import com.qiniu.utils.QiniuException;
-
-public abstract class JSONObjectRet extends CallRet {
- public JSONObjectRet(){}
- protected int mIdx;
- public JSONObjectRet(int idx) { mIdx = idx; }
- @Override
- public void onSuccess(byte[] body) {
- if (body == null) {
- onSuccess(new JSONObject());
- return;
- }
- try {
- JSONObject obj = new JSONObject(new String(body));
- onSuccess(obj);
- } catch (JSONException e) {
- e.printStackTrace();
- onFailure(new QiniuException(QiniuException.JSON, new String(body), e));
- }
- }
-
- public abstract void onSuccess(JSONObject obj);
-}
diff --git a/src/com/qiniu/conf/Conf.java b/src/com/qiniu/conf/Conf.java
index 25081e222..0b90aebb0 100644
--- a/src/com/qiniu/conf/Conf.java
+++ b/src/com/qiniu/conf/Conf.java
@@ -1,22 +1,44 @@
package com.qiniu.conf;
-import java.util.Random;
-
public class Conf {
- public static final String VERSION = "6.0.5";
+ public static final String VERSION = "6.1.0";
public static String UP_HOST = "http://upload.qiniu.com";
public static String UP_HOST2 = "http://up.qiniu.com";
-
- private static String id = genId();
-
- public static String getUserAgent() {
- return "QiniuAndroid/" + VERSION + " (" + android.os.Build.VERSION.RELEASE + "; "
- + android.os.Build.MODEL+ "; " + id +")";
- }
-
- private static String genId(){
- Random r = new Random();
- int rnum = r.nextInt(999);
- return System.currentTimeMillis() + "" + rnum;
- }
+
+ public static final String CHARSET = "utf-8";
+
+ /**
+ * HTTP连接超时的时间毫秒(ms)
+ * Determines the timeout in milliseconds until a connection is established.
+ * A timeout value of zero is interpreted as an infinite timeout.
+ *
+ * Please note this parameter can only be applied to connections that
+ * are bound to a particular local address.
+ */
+ public static int CONNECTION_TIMEOUT = 30 * 1000;
+
+ /**
+ * 读取response超时的时间毫秒(ms)
+ * Defines the socket timeout (SO_TIMEOUT) in milliseconds,
+ * which is the timeout for waiting for data or, put differently,
+ * a maximum period inactivity between two consecutive data packets).
+ * A timeout value of zero is interpreted as an infinite timeout.
+ * @see java.net.SocketOptions#SO_TIMEOUT
+ */
+ public static int SO_TIMEOUT = 30 * 1000;
+
+ public static final int BLOCK_SIZE = 1024 * 1024 * 4;
+ public static int CHUNK_SIZE = 1024 * 256;
+ public static int FIRST_CHUNK = 1024 * 256;
+ public static int ONCE_WRITE_SIZE = 1024 * 32;
+
+ public static int BLOCK_TRY_TIMES = 2;
+ public static int CHUNK_TRY_TIMES = 3;
+
+ public static String USER_AGENT = null;
+
+ public static final int ERROR_CODE = 0;
+ public static final int CANCEL_CODE = -1;
+ public static String PROCESS_MSG = "upload alread in process or procssed or canceled.";
+
}
diff --git a/src/com/qiniu/demo/MyActivity.java b/src/com/qiniu/demo/MyActivity.java
index a28b45c34..4d63a07e5 100644
--- a/src/com/qiniu/demo/MyActivity.java
+++ b/src/com/qiniu/demo/MyActivity.java
@@ -1,6 +1,7 @@
package com.qiniu.demo;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -8,27 +9,32 @@
import android.widget.Button;
import android.widget.TextView;
-import org.json.JSONObject;
-
import java.util.HashMap;
import com.qiniu.R;
-import com.qiniu.auth.JSONObjectRet;
+import com.qiniu.auth.Authorizer;
import com.qiniu.io.IO;
-import com.qiniu.io.PutExtra;
-import com.qiniu.utils.QiniuException;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.rs.UploadCallRet;
+/**
+ * 也可参考 UploadTest
+ */
public class MyActivity extends Activity implements View.OnClickListener{
public static final int PICK_PICTURE_RESUMABLE = 0;
+
+ public static String uptoken = "anEC5u_72gw1kZPSy3Dsq1lo_DPXyvuPDaj4ePkN:zmaikrTu1lgLb8DTvKQbuFZ5ai0=:eyJzY29wZSI6ImFuZHJvaWRzZGsiLCJyZXR1cm5Cb2R5Ijoie1wiaGFzaFwiOlwiJChldGFnKVwiLFwia2V5XCI6XCIkKGtleSlcIixcImZuYW1lXCI6XCIgJChmbmFtZSkgXCIsXCJmc2l6ZVwiOlwiJChmc2l6ZSlcIixcIm1pbWVUeXBlXCI6XCIkKG1pbWVUeXBlKVwiLFwieDphXCI6XCIkKHg6YSlcIn0iLCJkZWFkbGluZSI6MTQ2NjIyMjcwMX0=";
+ // upToken 这里需要自行获取. SDK 将不实现获取过程. 隔一段时间到业务服务器重新获取一次
+ public static Authorizer auth = new Authorizer();
+ {
+ auth.setUploadToken(uptoken);
+ }
- // @gist upload_arg
- // 在七牛绑定的对应bucket的域名. 默认是bucket.qiniudn.com
- public static String bucketName = "";
- public static String domain = bucketName + ".qiniudn.com";
- // upToken 这里需要自行获取. SDK 将不实现获取过程. 当token过期后才再获取一遍
- public String uptoken = "";
- // @endgist
+ // 在七牛绑定的对应bucket的域名. 更换 uptoken 时同时更换为对应的空间名,
+ public static String bucketName = "androidsdk";
private Button btnUpload;
private Button btnResumableUpload;
@@ -52,8 +58,9 @@ private void initWidget() {
btnResumableUpload.setOnClickListener(this);
}
+
// @gist upload
- boolean uploading = false;
+ volatile boolean uploading = false;
/**
* 普通上传文件
* @param uri
@@ -69,27 +76,28 @@ private void doUpload(Uri uri) {
extra.params = new HashMap();
extra.params.put("x:a", "测试中文信息");
hint.setText("上传中");
- IO.putFile(this, uptoken, key, uri, extra, new JSONObjectRet() {
+ // 返回 UploadTaskExecutor ,可执行cancel,见 MyResumableActivity
+ Context context = this.getApplicationContext();
+ IO.putFile(context, auth, key, uri, extra, new CallBack() {
@Override
public void onProcess(long current, long total) {
- hint.setText(current + "/" + total);
+ int percent = (int)(current*100/total);
+ hint.setText("上传中: " + current + "/" + total + " " + current/1024 + "K/" + total/1024 + "K; " + percent + "%");
}
@Override
- public void onSuccess(JSONObject resp) {
+ public void onSuccess(UploadCallRet ret) {
uploading = false;
- String hash = resp.optString("hash", "");
- String value = resp.optString("x:a", "");
- String redirect = "http://" + domain + "/" + hash;
- hint.setText("上传成功! " + hash);
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(redirect));
- startActivity(intent);
+ String key = ret.getKey();
+ String redirect = "http://" + bucketName + ".qiniudn.com/" + key;
+ String redirect2 ="http://" + bucketName + ".u.qiniudn.com/" + key;
+ hint.setText("上传成功! ret: " + ret.toString() + " \r\n可到" + redirect + " 或 " + redirect2 + " 访问");
}
@Override
- public void onFailure(QiniuException ex) {
+ public void onFailure(CallRet ret) {
uploading = false;
- hint.setText("错误: " + ex.getMessage());
+ hint.setText("错误: " + ret.toString());
}
});
}
@@ -98,8 +106,10 @@ public void onFailure(QiniuException ex) {
@Override
public void onClick(View view) {
if (view.equals(btnUpload)) {
- Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- startActivityForResult(i, PICK_PICTURE_RESUMABLE);
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("*/*");
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ startActivityForResult(intent, PICK_PICTURE_RESUMABLE);
return;
}
if (view.equals(btnResumableUpload)) {
@@ -116,4 +126,5 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
return;
}
}
+
}
diff --git a/src/com/qiniu/demo/MyResumableActivity.java b/src/com/qiniu/demo/MyResumableActivity.java
index d19ca1f3f..be9a32661 100644
--- a/src/com/qiniu/demo/MyResumableActivity.java
+++ b/src/com/qiniu/demo/MyResumableActivity.java
@@ -1,6 +1,7 @@
package com.qiniu.demo;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -8,19 +9,28 @@
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
-import android.widget.Toast;
-import org.json.JSONException;
-import org.json.JSONObject;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import com.qiniu.R;
-import com.qiniu.auth.JSONObjectRet;
-import com.qiniu.resumableio.PutExtra;
+import com.qiniu.auth.Authorizer;
import com.qiniu.resumableio.ResumableIO;
-import com.qiniu.utils.QiniuException;
+import com.qiniu.resumableio.SliceUploadTask.Block;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.rs.UploadCallRet;
+import com.qiniu.rs.UploadTaskExecutor;
public class MyResumableActivity extends Activity implements View.OnClickListener {
+
+ private Authorizer auth = new Authorizer();
+ {
+ auth.setUploadToken(MyActivity.uptoken);
+ }
+
private ProgressBar pb;
private Button start;
private Button stop;
@@ -50,55 +60,75 @@ public void onClick(View view) {
return;
}
if (view.equals(stop)) {
- if (uploadUri == null) {
- Toast.makeText(this, "还没开始任务", 20).show();
- return;
- }
- if (taskId >= 0) {
- ResumableIO.stop(taskId);
- stop.setText("开始");
- hint.setText("暂停");
- taskId = -1;
- return;
+ if(executor != null && uploading){
+ executor.cancel();
+ uploading = false;
+ clean();
+ stop.setText("PLAY");
+ }else{
+ stop.setText("STOP");
+ hint.setText("连接中");
+ doResumableUpload(uploadUri, mExtra);
}
- stop.setText("暂停");
- hint.setText("连接中");
- doResumableUpload(uploadUri, mExtra);
- return;
}
}
- int taskId = -1;
+
+ private void clean(){
+ executor = null;
+ }
+
+ volatile boolean uploading = false;
+ UploadTaskExecutor executor;
Uri uploadUri;
- PutExtra mExtra;
+ PutExtra mExtra = new PutExtra();
public void doResumableUpload(final Uri uri, PutExtra extra) {
+ uploadUri = uri;
+ final MyBlockRecord record = MyBlockRecord.genFromUri(this, uri);
+
hint.setText("连接中");
String key = null;
- String token = "";
- extra.params = new HashMap();
- extra.params.put("x:a", "bb");
- taskId = ResumableIO.putFile(this, token, key, uri, extra, new JSONObjectRet() {
+ if(extra != null){
+ extra.params = new HashMap();
+ extra.params.put("x:a", "bb");
+ }
+ List blks = record.loadBlocks();
+ String s = "blks.size(): " + blks.size() + " ==> ";
+ for(Block blk : blks ){
+ s += blk.getIdx() + ", ";
+ }
+ final String pre = s + "\r\n";
+ uploading = true;
+ executor = ResumableIO.putFile(this, auth, key, uri, extra, blks, new CallBack() {
@Override
- public void onSuccess(JSONObject obj) {
- hint.setText("上传成功: " + obj.optString("key", ""));
+ public void onSuccess(UploadCallRet ret) {
+ uploading = false;
+ String key = ret.getKey();
+ String redirect = "http://" + MyActivity.bucketName + ".qiniudn.com/" + key;
+ String redirect2 ="http://" + MyActivity.bucketName + ".u.qiniudn.com/" + key;
+ hint.setText(pre + "上传成功! ret: " + ret.toString() + " \r\n可到" + redirect + " 或 " + redirect2 + " 访问");
+ record.removeBlocks();
+ clean();
}
@Override
public void onProcess(long current, long total) {
- float percent = (float) (current*10000/total) / 100;
- hint.setText("上传中: " + percent + "%");
+ int percent = (int)(current*100/total);
+ hint.setText(pre + "上传中: " + current + "/" + total + " " + current/1024 + "K/" + total/1024 + "K; " + percent + "%");
+ //int i = 3/0;
pb.setProgress((int) percent);
}
@Override
- public void onPause(Object tag) {
- uploadUri = uri;
- mExtra = (PutExtra) tag;
+ public void onBlockSuccess(Block blk){
+ record.saveBlock(blk);
}
@Override
- public void onFailure(QiniuException ex) {
- hint.setText(ex.getMessage());
+ public void onFailure(CallRet ret) {
+ uploading = false;
+ clean();
+ hint.setText(pre + "错误: " + ret.toString());
}
});
}
@@ -107,21 +137,6 @@ public void onFailure(QiniuException ex) {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) return;
PutExtra e = new PutExtra();
- e.notify = new PutExtra.INotify() {
- @Override
- public void onSuccessUpload(PutExtra ex) {
- if (ex.isFinishAll()) return;
- JSONObject json;
- try {
- json = ex.toJSON();
- } catch (JSONException e1) {
- e1.printStackTrace();
- return;
- }
- // store to disk
- // restore PutExtra by new PutExtra(JSONObject);
- }
- };
doResumableUpload(data.getData(), e);
}
@@ -135,4 +150,45 @@ public void selectFile() {
ex.printStackTrace();
}
}
+
+ static class MyBlockRecord{
+ private static HashMap> records = new HashMap>();
+
+ public static MyBlockRecord genFromUri(Context context, Uri uri){
+ String id = uri.getPath() + context.toString();
+ return new MyBlockRecord(id);
+ }
+
+ private final String id;
+ private List lastUploadBlocks;
+ public MyBlockRecord(String id){
+ this.id = id;
+ }
+
+ public List loadBlocks() {
+ if(lastUploadBlocks == null){
+ List t = records.get(id);
+ if(t == null){
+ t = new ArrayList();
+ records.put(id, t);
+ }
+ lastUploadBlocks = t;
+ }
+ return lastUploadBlocks;
+ }
+
+ /**
+ * @param blk 断点记录, 以4M为一个断点单元
+ */
+ public void saveBlock(Block blk){
+ if(lastUploadBlocks != null){
+ lastUploadBlocks.add(blk);
+ }
+ }
+
+ public void removeBlocks(){
+ records.remove(id);
+ }
+ }
+
}
diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java
index 04d9634cf..2075274dd 100644
--- a/src/com/qiniu/io/IO.java
+++ b/src/com/qiniu/io/IO.java
@@ -1,175 +1,53 @@
package com.qiniu.io;
-import android.content.Context;
-import android.net.Uri;
-
-import org.json.JSONObject;
-
import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Map;
+import android.content.Context;
+import android.net.Uri;
-import com.qiniu.auth.CallRet;
-import com.qiniu.auth.Client;
-import com.qiniu.auth.JSONObjectRet;
+import com.qiniu.auth.Authorizer;
import com.qiniu.conf.Conf;
-import com.qiniu.utils.IOnProcess;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.rs.UploadTaskExecutor;
import com.qiniu.utils.InputStreamAt;
-import com.qiniu.utils.MultipartEntity;
-import com.qiniu.utils.FileUri;
-import com.qiniu.utils.QiniuException;
-import com.qiniu.utils.RetryRet;
public class IO {
public static String UNDEFINED_KEY = null;
- private static Client mClient;
- private static String mUptoken;
- private static long mClientUseTime;
- public IO(Client client, String uptoken) {
- mClient = client;
- mUptoken = uptoken;
- }
-
- private static Client defaultClient() {
- if (mClient != null && System.currentTimeMillis() - mClientUseTime > 3 * 60 * 1000) { // 1 minute
- mClient.close();
- mClient = null;
- }
- if (mClient == null) {
- mClient = Client.defaultClient();
- }
- mClientUseTime = System.currentTimeMillis();
- return mClient;
- }
-
- private static MultipartEntity buildMultipartEntity(String key, InputStreamAt isa, PutExtra extra) throws IOException {
- MultipartEntity m = new MultipartEntity();
- if (key != null) {
- m.addField("key", key);
- }
- if (extra.checkCrc == PutExtra.AUTO_CRC32) {
- extra.crc32 = isa.crc32();
- }
- if (extra.checkCrc != PutExtra.UNUSE_CRC32) {
- m.addField("crc32", extra.crc32 + "");
- }
- for (Map.Entry i: extra.params.entrySet()) {
- m.addField(i.getKey(), i.getValue());
+
+ public static UploadTaskExecutor putFile(Context mContext,
+ Authorizer auth, String key, Uri uri, PutExtra extra, CallBack callback) {
+ try {
+ return put(auth, key, InputStreamAt.fromUri(mContext, uri), extra, callback);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
}
-
- m.addField("token", mUptoken);
- m.addFile("file", extra.mimeType, key == null ? "?" : key, isa);
- return m;
}
-
- /**
- * 上传二进制
- *
- * @param key 键值名, UNDEFINED_KEY 表示自动生成key
- * @param isa 二进制数据
- * @param extra 上传参数
- * @param ret 回调函数
- */
- public void put(String key, final InputStreamAt isa, PutExtra extra, final JSONObjectRet ret) {
- final MultipartEntity m;
+
+ public static UploadTaskExecutor putFile(Authorizer auth, String key,
+ File file, PutExtra extra, CallBack callback) {
try {
- m = buildMultipartEntity(key, isa, extra);
- } catch (IOException e) {
- ret.onFailure(new QiniuException(QiniuException.IO, "build multipart", e));
- return;
+ return put(auth, key, InputStreamAt.fromFile(file), extra, callback);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
}
-
- final Client client = defaultClient();
- final Client.ClientExecutor executor = client.makeClientExecutor();
- m.setProcessNotify(new IOnProcess() {
- @Override
- public void onProcess(long current, long total) {
- executor.upload(current, total);
- }
-
- @Override
- public void onFailure(QiniuException ex) {
- executor.onFailure(ex);
- }
- });
-
- CallRet retryRet = new RetryRet(ret){
- @Override
- public void onFailure(QiniuException ex) {
- if (RetryRet.noRetry(ex)){
- ret.onFailure(ex);
- return;
- }
- isa.reset();
- Client.ClientExecutor executor2 = client.makeClientExecutor();
- client.call(executor2, Conf.UP_HOST2, m, ret);
- }
- };
- client.call(executor, Conf.UP_HOST, m, retryRet);
}
- /**
- * 通过提供URI来上传指定的文件
- *
- * @param mContext
- * @param key
- * @param uri 通过图库或其他拿到的URI
- * @param extra 上传参数
- * @param ret 结果回调函数
- */
- public void putFile(Context mContext, String key, Uri uri, PutExtra extra, JSONObjectRet ret) {
- File file = FileUri.getFile(mContext, uri);
- if (!file.exists()) {
- ret.onFailure(QiniuException.fileNotFound(uri.toString()));
- return;
+ public static UploadTaskExecutor put(Authorizer auth,
+ String key, InputStreamAt input, PutExtra extra, CallBack callback) {
+ try {
+ SimpleUploadTask task = new SimpleUploadTask(auth, input, key, extra, callback);
+ task.execute();
+ return new UploadTaskExecutor(task);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
}
- putFile(key, file, extra, ret);
- }
-
- public void putFile(String key, File file, PutExtra extra, JSONObjectRet ret) {
- putAndClose(key, InputStreamAt.fromFile(file), extra, ret);
- }
-
- private void putAndClose(final String key, final InputStreamAt input, final PutExtra extra, final JSONObjectRet ret) {
- JSONObjectRet closer = new JSONObjectRet() {
- @Override
- public void onSuccess(JSONObject obj) {
- input.close();
- ret.onSuccess(obj);
- }
-
- @Override
- public void onProcess(long current, long total) {
- ret.onProcess(current, total);
- }
-
- @Override
- public void onPause(Object tag) {
- ret.onPause(tag);
- }
-
- @Override
- public void onFailure(QiniuException ex) {
- input.close();
- ret.onFailure(ex);
- }
- };
- put(key, input, extra, closer);
}
- public static void put(String uptoken, String key, InputStreamAt input, PutExtra extra, JSONObjectRet callback) {
- new IO(defaultClient(), uptoken).put(key, input, extra, callback);
- }
-
- public static void putFile(Context mContext, String uptoken, String key, Uri uri, PutExtra extra, JSONObjectRet callback) {
- new IO(defaultClient(), uptoken).putFile(mContext, key, uri, extra, callback);
- }
- public static void putFile(String uptoken, String key, File file, PutExtra extra, JSONObjectRet callback) {
- new IO(defaultClient(), uptoken).putFile(key, file, extra, callback);
- }
}
diff --git a/src/com/qiniu/io/SimpleUploadTask.java b/src/com/qiniu/io/SimpleUploadTask.java
new file mode 100644
index 000000000..5ddaddbfd
--- /dev/null
+++ b/src/com/qiniu/io/SimpleUploadTask.java
@@ -0,0 +1,90 @@
+package com.qiniu.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.http.HttpResponse;
+
+import com.qiniu.auth.Authorizer;
+import com.qiniu.conf.Conf;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.utils.IOnProcess;
+import com.qiniu.utils.InputStreamAt;
+import com.qiniu.utils.MultipartEntity;
+import com.qiniu.utils.UploadTask;
+import com.qiniu.utils.Util;
+
+
+public class SimpleUploadTask extends UploadTask {
+
+ public SimpleUploadTask(Authorizer auth, InputStreamAt isa, String key,
+ PutExtra extra, CallBack ret) throws IOException {
+ super(auth, isa, key, extra, ret);
+ }
+
+ @Override
+ protected CallRet execDoInBackground(Object... arg0) {
+ CallRet ret = upload(Conf.UP_HOST);
+ if(Util.needChangeUpAdress(ret)){
+ try {
+ orginIsa.reset();
+ ret = upload(Conf.UP_HOST2);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return ret;
+ }
+
+ private CallRet upload(String url){
+ try{
+ post = Util.newPost(url);
+ post.setEntity(buildHttpEntity());
+ HttpResponse response = getHttpClient().execute(post);
+ CallRet ret = Util.handleResult(response);
+ return ret;
+ }catch(Exception e){
+ return new CallRet(Conf.ERROR_CODE, "", e);
+ }finally{
+ post = null;
+ }
+ }
+
+ private MultipartEntity buildHttpEntity() throws IOException {
+ MultipartEntity m = new MultipartEntity();
+ if (key != null) {
+ m.addField("key", key);
+ }
+ if (extra.checkCrc == PutExtra.AUTO_CRC32) {
+ extra.crc32 = orginIsa.crc32();
+ }
+ if (extra.checkCrc != PutExtra.UNUSE_CRC32) {
+ m.addField("crc32", extra.crc32 + "");
+ }
+ if(extra.params != null){
+ for (Map.Entry i: extra.params.entrySet()) {
+ if(i.getKey().startsWith("x:")){
+ m.addField(i.getKey(), i.getValue());
+ }
+ }
+ }
+
+ m.addField("token", auth.getUploadToken());
+ m.addFile("file", extra.mimeType, orginIsa.getFilename(), orginIsa);
+
+
+ m.setProcessNotify(new IOnProcess(){
+
+ @Override
+ public void onProcess(long current, long total) {
+ publishProgress(current, total);
+ }
+
+ });
+
+ return m;
+ }
+
+}
diff --git a/src/com/qiniu/resumableio/PutExtra.java b/src/com/qiniu/resumableio/PutExtra.java
deleted file mode 100644
index 67b38acb4..000000000
--- a/src/com/qiniu/resumableio/PutExtra.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.qiniu.resumableio;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-public class PutExtra {
- public Map params;
- public PutRet[] processes;
- public String mimeType;
- public INotify notify;
-
- long totalSize;
-
- public PutExtra() {}
- public PutExtra(JSONObject obj) {
- mimeType = obj.optString("mimeType", "");
- JSONArray procs = obj.optJSONArray("processes");
- processes = new PutRet[procs.length()];
- for (int i=0; i();
- JSONObject paramsJson = obj.optJSONObject("params");
- for (Iterator> iter = paramsJson.keys(); iter.hasNext();) {
- String key = (String) iter.next();
- params.put(key, paramsJson.optString(key));
- }
- }
-
- public boolean isFinishAll() {
- if (totalSize <= 0) return false;
- long currentSize = 0;
- for (PutRet pr: processes) {
- currentSize += pr.offset;
- }
- return currentSize >= totalSize;
- }
-
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
- JSONArray process = new JSONArray();
- for (PutRet p: processes) {
- process.put(p.toJSON());
- }
- json.put("processes", process);
- json.put("mimeType", mimeType);
- if (params != null) json.put("params", new JSONObject(params));
- return json;
- }
- public interface INotify {
- public void onSuccessUpload(PutExtra ex);
- }
-}
diff --git a/src/com/qiniu/resumableio/PutRet.java b/src/com/qiniu/resumableio/PutRet.java
deleted file mode 100644
index 1ef66318a..000000000
--- a/src/com/qiniu/resumableio/PutRet.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.qiniu.resumableio;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class PutRet {
- public String ctx;
- public String host;
- public long crc32;
- public String checksum;
- public int offset = 0;
- public boolean isInvalid() {
- return ctx == null || offset == 0;
- }
- public PutRet(){}
- public PutRet(JSONObject obj) {parse(obj);}
- public PutRet parse(JSONObject obj) {
- ctx = obj.optString("ctx", "");
- host = obj.optString("host", "");
- crc32 = Long.valueOf(obj.optString("crc32", "0"));
- checksum = obj.optString("checksum", "");
- offset = obj.optInt("offset", 0);
- return this;
- }
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
- json.put("crc32", crc32);
- json.put("checksum", checksum);
- json.put("offset", offset);
- json.put("host", host);
- json.put("ctx", ctx);
- return json;
- }
-}
diff --git a/src/com/qiniu/resumableio/ResumableClient.java b/src/com/qiniu/resumableio/ResumableClient.java
deleted file mode 100644
index ebf15ac48..000000000
--- a/src/com/qiniu/resumableio/ResumableClient.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package com.qiniu.resumableio;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.entity.StringEntity;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.Map;
-
-import com.qiniu.auth.CallRet;
-import com.qiniu.auth.Client;
-import com.qiniu.auth.JSONObjectRet;
-import com.qiniu.conf.Conf;
-import com.qiniu.utils.ICancel;
-import com.qiniu.utils.InputStreamAt;
-import com.qiniu.utils.Base64;
-import com.qiniu.utils.QiniuException;
-import com.qiniu.utils.RetryRet;
-
-public class ResumableClient extends Client {
- String mUpToken;
- int CHUNK_SIZE = 256 * 1024;
- int BLOCK_SIZE = 4 * 1024 * 1024;
- public ResumableClient(HttpClient client, String uptoken) {
- super(client);
- mUpToken = uptoken;
- }
-
- @Override
- protected HttpResponse roundtrip(HttpRequestBase httpRequest) throws IOException {
- if (mUpToken != null) {
- httpRequest.setHeader("Authorization", "UpToken " + mUpToken);
- }
- return super.roundtrip(httpRequest);
- }
-
- public ICancel[] putblock(final InputStreamAt input, final PutExtra extra, final PutRet putRet, final long offset, final JSONObjectRet callback) {
- final int writeNeed = (int) Math.min(input.length()-offset, BLOCK_SIZE);
- final ICancel[] canceler = new ICancel[] {null};
- JSONObjectRet ret = new JSONObjectRet() {
- long crc32, wrote, writing = 0;
- public void onInit(int flag) {
- flag = putRet.isInvalid() ? 0 : 1;
- if (flag == 0) putInit();
- if (flag == 1) putNext();
- }
-
- public void putInit() {
- int chunkSize = Math.min(writeNeed, CHUNK_SIZE);
- try {
- crc32 = input.partCrc32(offset, chunkSize);
- } catch (IOException e) {
- onFailure(new QiniuException(QiniuException.IO, "crc IOException", e));
- return;
- }
- canceler[0] = mkblk(input, offset, writeNeed, chunkSize, this);
- }
-
- public void putNext() {
- wrote = putRet.offset;
- int remainLength = Math.min((int) (input.length() - offset - putRet.offset), CHUNK_SIZE);
- try {
- crc32 = input.partCrc32(offset+putRet.offset, remainLength);
- } catch (IOException e) {
- onFailure(new QiniuException(QiniuException.IO, "next crc IOException", e));
- return;
- }
- canceler[0] = bput(putRet.host, input, putRet.ctx, offset, putRet.offset, remainLength, this);
- }
-
- @Override
- public void onSuccess(JSONObject obj) {
- if (crc32 != new PutRet(obj).crc32) {
- onInit(-1);
- return;
- }
- putRet.parse(obj);
- if (extra.notify != null) extra.notify.onSuccessUpload(extra);
- wrote += writing;
- if (putRet.offset == writeNeed) {
- callback.onSuccess(obj);
- return;
- }
- putNext();
- }
-
- @Override
- public void onProcess(long current, long total) {
- writing = current;
- callback.onProcess(wrote+writing, writeNeed);
- }
-
- @Override
- public void onFailure(QiniuException ex) {
- callback.onFailure(ex);
- }
- };
- ret.onInit(-1);
- return canceler;
- }
-
- public ICancel mkblk(final InputStreamAt input, final long offset, final int blockSize, final int writeSize, final CallRet ret) {
- String url = Conf.UP_HOST + "/mkblk/" + blockSize;
- ClientExecutor executor = makeClientExecutor();
- CallRet retryRet = new RetryRet(ret){
- @Override
- public void onFailure(QiniuException ex) {
- if (RetryRet.noRetry(ex)){
- ret.onFailure(ex);
- return;
- }
- ClientExecutor executor2 = makeClientExecutor();
- String url2 = Conf.UP_HOST2 + "/mkblk/" + blockSize;
- call(executor2, url2, input.toHttpEntity(offset, writeSize, executor2), ret);
- }
- };
-
- call(executor, url, input.toHttpEntity(offset, writeSize, executor), retryRet);
- return executor;
- }
-
- public ICancel bput(String host, InputStreamAt input, String ctx, long blockOffset, long offset, int writeLength, CallRet ret) {
- String url = host + "/bput/" + ctx + "/" + offset;
- ClientExecutor client = makeClientExecutor();
-
- call(client, url, input.toHttpEntity(blockOffset+offset, writeLength, client), ret);
- return client;
- }
-
- public ICancel mkfile(final String key, final long fsize, final String mimeType, final Map params, final String ctxs, final CallRet ret) {
- String url = Conf.UP_HOST + mkfilePath(key, fsize, mimeType, params);
- StringEntity entity = null;
- try {
- entity = new StringEntity(ctxs);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- ret.onFailure(new QiniuException(QiniuException.InvalidEncode, "mkfile", e));
- return null;
- }
-
- CallRet retryRet = new RetryRet(ret){
- @Override
- public void onFailure(QiniuException ex) {
- if (RetryRet.noRetry(ex)){
- ret.onFailure(ex);
- return;
- }
- String url2 = Conf.UP_HOST2 + mkfilePath(key, fsize, mimeType, params);
- StringEntity entity2 = null;
- try {
- entity2 = new StringEntity(ctxs);
- } catch (UnsupportedEncodingException e) {
- }
- call(makeClientExecutor(), url2, entity2, ret);
- }
- };
- return call(makeClientExecutor(), url, entity, retryRet);
- }
-
- private static String mkfilePath(String key, long fsize, String mimeType, Map params){
- String path = "/mkfile/" + fsize;
- if (mimeType != null && mimeType.length() > 0) {
- path += "/mimeType/" + Base64.encode(mimeType);
- }
- if (key != null && key.length() > 0) {
- path += "/key/" + Base64.encode(key);
- }
- if (params != null && params.size() > 0) {
- for (Map.Entry a: params.entrySet()) {
- path += "/" + a.getKey() + "/" + Base64.encode(a.getValue());
- }
- }
- return path;
- }
-}
diff --git a/src/com/qiniu/resumableio/ResumableIO.java b/src/com/qiniu/resumableio/ResumableIO.java
index 037cfdef9..1e60064ef 100644
--- a/src/com/qiniu/resumableio/ResumableIO.java
+++ b/src/com/qiniu/resumableio/ResumableIO.java
@@ -1,186 +1,69 @@
package com.qiniu.resumableio;
+import java.io.File;
+import java.util.List;
+
import android.content.Context;
import android.net.Uri;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-
-import com.qiniu.auth.Client;
-import com.qiniu.auth.JSONObjectRet;
-import com.qiniu.utils.ICancel;
+import com.qiniu.auth.Authorizer;
+import com.qiniu.conf.Conf;
+import com.qiniu.resumableio.SliceUploadTask.Block;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.rs.UploadTaskExecutor;
import com.qiniu.utils.InputStreamAt;
-import com.qiniu.utils.FileUri;
-import com.qiniu.utils.QiniuException;
public class ResumableIO {
- ResumableClient mClient;
- private int BLOCK_SIZE = 4 * 1024 * 1024;
- private static int atomicId = 0;
- static HashMap idCancels = new HashMap();
-
- public ResumableIO(String uptoken) {
- mClient = new ResumableClient(Client.getMultithreadClient(), uptoken);
- }
- public ResumableIO(ResumableClient client) {
- mClient = client;
- }
-
- private synchronized Integer newTask(ICancel c) {
- idCancels.put(atomicId, c);
- return atomicId++;
+
+ public static UploadTaskExecutor putFile(Context mContext,
+ Authorizer auth, String key, Uri uri, PutExtra extra, CallBack callback) {
+ return putFile(mContext, auth, key, uri, extra, null, callback);
}
-
- private synchronized void removeTask(Integer id) {
- idCancels.remove(id);
- }
-
- private int putAndClose(final String key, final InputStreamAt input, final PutExtra extra, final JSONObjectRet ret) {
- return put(key, input, extra, new JSONObjectRet() {
- @Override
- public void onSuccess(JSONObject obj) {
- input.close();
- ret.onSuccess(obj);
- }
-
- @Override
- public void onProcess(long current, long total) {
- ret.onProcess(current, total);
- }
-
- @Override
- public void onPause(Object tag) {
- ret.onPause(tag);
- }
-
- @Override
- public void onFailure(QiniuException ex) {
- input.close();
- ret.onFailure(ex);
- }
- });
- }
-
- public int put(final String key, final InputStreamAt input, final PutExtra extra, final JSONObjectRet ret) {
- final int blkCount = (int) ((input.length() + BLOCK_SIZE - 1) / BLOCK_SIZE);
- if (extra.processes == null) extra.processes = new PutRet[blkCount];
- extra.totalSize = input.length();
- final int[] success = new int[] {0};
- final long[] uploaded = new long[blkCount];
- final ICancel[][] cancelers = new ICancel[blkCount][1];
- final boolean[] failure = new boolean[] {false};
- final int taskId = newTask(new ICancel() {
-
- @Override
- public boolean cancel(boolean isIntercupt) {
- for (ICancel[] cancel: cancelers) {
- if (cancel == null || cancel[0] == null) continue;
- cancel[0].cancel(true);
- }
- failure[0] = true;
- ret.onPause(extra);
- return false;
- }
- });
- for (int i=0; i 0) ctx = ctx.substring(1);
- removeTask(taskId);
- mClient.mkfile(key, input.length(), extra.mimeType, extra.params, ctx, ret);
- }
-
- @Override
- public void onSuccess(JSONObject obj) {
- if (failure[0] || ++success[0] != blkCount) return;
- onAllSuccess();
- }
-
- @Override
- public void onProcess(long current, long total) {
- if (failure[0]) return;
- uploaded[mIdx] = current;
- current = 0;
- for (long c: uploaded) current += c;
- ret.onProcess(current, input.length());
- }
-
- @Override
- public void onFailure(QiniuException ex) {
- if (failure[0]) {
- ex.printStackTrace();
- return;
- }
- if (--retryTime <= 0 || (ex.getMessage() != null && ex.getMessage().contains("Unauthorized"))) {
- removeTask(taskId);
- failure[0] = true;
- ret.onFailure(ex);
- return;
- }
- if (ex.getMessage() != null && ex.getMessage().contains("invalid BlockCtx")) {
- uploaded[mIdx] = 0;
- extra.processes[mIdx] = new PutRet();
- }
- cancelers[mIdx] = mClient.putblock(input, extra, extra.processes[mIdx], startPos, this);
- }
- });
+
+ public static UploadTaskExecutor putFile(Context mContext,
+ Authorizer auth, String key, Uri uri, PutExtra extra,
+ List blocks, CallBack callback) {
+ try {
+ return put(auth, key, InputStreamAt.fromUri(mContext, uri), extra, blocks, callback);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
}
-
- return taskId;
}
-
- public int putFile(String key, File file, PutExtra extra, final JSONObjectRet ret) {
- return putAndClose(key, InputStreamAt.fromFile(file), extra, ret);
+
+ public static UploadTaskExecutor putFile(Authorizer auth, String key,
+ File file, PutExtra extra, CallBack callback) {
+ return putFile(auth, key, file, extra, null, callback);
}
-
- public int putFile(Context mContext, String key, Uri uri, PutExtra extra, final JSONObjectRet ret) {
- File file = FileUri.getFile(mContext, uri);
- if (!file.exists()) {
- ret.onFailure(QiniuException.fileNotFound(uri.toString()));
- return -1;
+
+ public static UploadTaskExecutor putFile(Authorizer auth, String key,
+ File file, PutExtra extra, List blocks, CallBack callback) {
+ try{
+ return put(auth, key, InputStreamAt.fromFile(file), extra, blocks, callback);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
}
-
- return putAndClose(key, InputStreamAt.fromFile(file), extra, ret);
}
- public synchronized static void stop(int id) {
- ICancel c = idCancels.get(id);
- if (c == null) return;
- c.cancel(true);
- idCancels.remove(id);
+ public static UploadTaskExecutor put(Authorizer auth, String key,
+ InputStreamAt input, PutExtra extra, CallBack callback) {
+ return put(auth, key, input, extra, null, callback);
}
-
- public static ResumableIO defaultInstance(String uptoken) {
- return new ResumableIO(new ResumableClient(Client.getMultithreadClient(), uptoken));
- }
-
- public static int putAndClose(String uptoken, String key, InputStreamAt isa, PutExtra extra, JSONObjectRet ret) {
- return ResumableIO.defaultInstance(uptoken).putAndClose(uptoken, key, isa, extra, ret);
- }
-
- public static int put(String uptoken, String key, InputStreamAt isa, PutExtra extra, JSONObjectRet ret) {
- return ResumableIO.defaultInstance(uptoken).put(key, isa, extra, ret);
- }
-
- public static int putFile(Context mContext, String uptoken, String key, Uri uri, PutExtra extra, JSONObjectRet ret) {
- return ResumableIO.defaultInstance(uptoken).putFile(mContext, key, uri, extra, ret);
+
+ public static UploadTaskExecutor put(Authorizer auth, String key,
+ InputStreamAt input, PutExtra extra, List blocks, CallBack callback) {
+ try {
+ SliceUploadTask task = new SliceUploadTask(auth, input, key, extra, callback);
+ task.setLastUploadBlocks(blocks);
+ task.execute();
+ return new UploadTaskExecutor(task);
+ } catch (Exception e) {
+ callback.onFailure(new CallRet(Conf.ERROR_CODE, "", e));
+ return null;
+ }
}
}
diff --git a/src/com/qiniu/resumableio/SliceUploadTask.java b/src/com/qiniu/resumableio/SliceUploadTask.java
new file mode 100644
index 000000000..c83fd1a5c
--- /dev/null
+++ b/src/com/qiniu/resumableio/SliceUploadTask.java
@@ -0,0 +1,361 @@
+package com.qiniu.resumableio;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+
+import com.qiniu.auth.Authorizer;
+import com.qiniu.conf.Conf;
+import com.qiniu.rs.CallRet;
+import com.qiniu.rs.ChunkUploadCallRet;
+import com.qiniu.rs.CallBack;
+import com.qiniu.rs.PutExtra;
+import com.qiniu.utils.Crc32;
+import com.qiniu.utils.InputStreamAt;
+import com.qiniu.utils.InputStreamAt.Input;
+import com.qiniu.utils.UploadTask;
+import com.qiniu.utils.Util;
+
+public class SliceUploadTask extends UploadTask {
+ private List lastUploadBlocks;
+
+ private volatile long uploadLength = 0;
+
+ public SliceUploadTask(Authorizer auth, InputStreamAt isa, String key,
+ PutExtra extra, CallBack ret) throws IOException {
+ super(auth, isa, key, extra, ret);
+ }
+
+
+ public void setLastUploadBlocks(List blocks) {
+ lastUploadBlocks = blocks;
+ }
+
+ @Override
+ protected CallRet execDoInBackground(Object... arg0) {
+ try{
+ List blks = new ArrayList();
+ CallRet ret = uploadBlocks(blks);
+ if(ret == null){
+ ret = mkfile(blks);
+ }
+ return ret;
+ } catch (Exception e) {
+ return new CallRet(Conf.ERROR_CODE, "", e);
+ }
+ }
+
+ protected void clean() {
+ super.clean();
+ lastUploadBlocks = null;
+ }
+
+ private CallRet uploadBlocks(List blks) throws IOException{
+ final long len = orginIsa.length();
+ final int blkCount = (int) ((len + Conf.BLOCK_SIZE - 1) / Conf.BLOCK_SIZE);
+
+ for(int i=0; i < blkCount; i++){
+ int l = (int)Math.min(Conf.BLOCK_SIZE, len - Conf.BLOCK_SIZE * i);
+ Block blk = getFromLastUploadedBlocks(i);
+ Input data = orginIsa.readNext(l);
+ if(blk != null){
+ data = null;
+ blks.add(blk);
+ addUpLength(l);
+ }else{
+ ChunkUploadCallRet chunkRet = upBlock(l, data, Conf.CHUNK_TRY_TIMES);
+ data = null;
+ if(!chunkRet.isOk()){
+ return chunkRet;
+ }
+ Block nblk = new Block(i, chunkRet.getCtx(), l, chunkRet.getHost());
+ blks.add(nblk);
+ this.publishProgress(nblk);
+ }
+ //data 最大为4M, 尽快回收。
+ data = null;
+ }
+
+ return null;
+ }
+
+
+ private ChunkUploadCallRet upBlock(int len, Input data, int time) throws IOException {
+ UpBlock blkUp = new UpBlock(this, len, data);
+ ChunkUploadCallRet chunkRet = blkUp.exec();
+ if(!chunkRet.isOk()){
+ addUpLength(-blkUp.getUpTotal());
+ if(chunkRet.getStatusCode() == 701 && time > 0){
+ blkUp = null;
+ chunkRet = null;
+ return upBlock(len, data, time - 1);
+ }
+ }
+ return chunkRet;
+ }
+
+ private void addUpLength(long len){
+ uploadLength += len;
+ this.publishProgress(uploadLength, contentLength);
+ }
+
+ private Block getFromLastUploadedBlocks(int idx){
+ if(lastUploadBlocks == null){
+ return null;
+ }
+ for(Block block : lastUploadBlocks){
+ if(idx == block.getIdx()){
+ return block;
+ }
+ }
+ return null;
+ }
+
+
+ private CallRet mkfile(List rets) {
+ String ctx = mkCtx(rets);
+ String lastHost = rets.get(rets.size() - 1).getHost();
+ return mkfile(ctx, lastHost, Conf.CHUNK_TRY_TIMES + 1);
+ }
+
+ private String mkCtx(List rets) {
+ StringBuffer sb = new StringBuffer();
+ for (Block ret : rets) {
+ sb.append(",").append(ret.getCtx());
+ }
+ return sb.substring(1);
+ }
+
+ private CallRet mkfile(String ctx, String lastHost, int time) {
+ try {
+ String url = buildMkfileUrl(lastHost);
+ HttpPost post = Util.newPost(url);
+ post.setHeader("Authorization", "UpToken " + auth.getUploadToken());
+ post.setEntity(new StringEntity(ctx));
+ HttpResponse response = getHttpClient().execute(post);
+ CallRet ret = Util.handleResult(response);
+ // 500 服务端失败; 579 回调失败
+ if (ret.getStatusCode() != 579 && ret.getStatusCode() / 100 == 5
+ && time > 0) {
+ return mkfile(ctx, lastHost, time - 1);
+ }
+ return ret;
+ } catch (Exception e) {
+ if (time > 0) {
+ return mkfile(ctx, lastHost, time - 1);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String buildMkfileUrl(String lastHost) {
+ StringBuilder url = new StringBuilder(lastHost + "/mkfile/" + contentLength);
+ if (null != key) {
+ url.append("/key/").append(Util.urlsafeBase64(key));
+ }
+ if (null != extra.mimeType && extra.mimeType.trim().length() != 0) {
+ url.append("/mimeType/").append(Util.urlsafeBase64(extra.mimeType));
+ }
+ if(extra.params != null){
+ for (Map.Entry xvar : extra.params.entrySet()) {
+ if (xvar.getKey().startsWith("x:")) {
+ url.append("/").append(xvar.getKey()).append("/").
+ append(Util.urlsafeBase64(xvar.getValue()));
+ }
+ }
+ }
+ return url.toString();
+ }
+
+
+ public static class Block{
+ private final int idx;
+ private final String ctx;
+ private final long length;
+ private final String host;
+
+ public Block(int idx, String ctx, long length, String host){
+ this.idx = idx;
+ this.ctx = ctx;
+ this.length = length;
+ this.host = host;
+ }
+
+ public int getIdx() {
+ return idx;
+ }
+
+ public String getCtx() {
+ return ctx;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public long getLength(){
+ return length;
+ }
+ }
+
+
+ static class UpBlock{
+ private Input blkData;
+ private SliceUploadTask task;
+ private volatile String orginHost = Conf.UP_HOST;
+ private volatile int total = 0;
+ private final int length;
+
+ public int getUpTotal(){
+ return total;
+ }
+
+ UpBlock(SliceUploadTask task, int len, Input isa){
+ this.task = task;
+ this.length = len;
+ this.blkData = isa;
+ }
+
+ ChunkUploadCallRet exec() throws IOException{
+ try{
+ return uploadChunk();
+ }finally{
+ blkData = null;
+ task = null;
+ orginHost = null;
+ }
+ }
+
+ ChunkUploadCallRet uploadChunk() throws IOException{
+ final int FIRST_CHUNK = Conf.FIRST_CHUNK;
+ final int CHUNK_SIZE = Conf.CHUNK_SIZE;
+
+ int flen = (int)Math.min(length, FIRST_CHUNK);
+ ChunkUploadCallRet ret = uploadMkblk(length, blkData.readNext(flen));
+ if(!ret.isOk()){
+ return ret;
+ }
+ addContentLength(flen);
+ if (length > FIRST_CHUNK) {
+ final int count = (int)((length - FIRST_CHUNK + CHUNK_SIZE - 1) / CHUNK_SIZE);
+ for(int i = 0; i < count; i++) {
+ if(task.isCancelled()){
+ return new ChunkUploadCallRet(Conf.CANCEL_CODE, "", Conf.PROCESS_MSG);
+ }
+ int start = CHUNK_SIZE * i + FIRST_CHUNK;
+ int len = (int)Math.min(length - start, CHUNK_SIZE);
+ ret = uploadChunk(ret, blkData.readNext(len));
+ if(!ret.isOk()){
+ return ret;
+ }
+ addContentLength(len);
+ }
+ }
+ return ret;
+ }
+
+ private void addContentLength(int len){
+ total += len;
+ task.addUpLength(len);
+ }
+
+ private ChunkUploadCallRet uploadMkblk(long blkLength, byte[] chunkData) {
+ return uploadMkblk(blkLength, chunkData, Conf.CHUNK_TRY_TIMES);
+ }
+
+ private ChunkUploadCallRet uploadMkblk(long blkLength, byte[] chunkData, int time){
+ String url = getMkblkUrl(blkLength);
+ ChunkUploadCallRet ret = upload(url, chunkData, time);
+ if(!ret.isOk()){
+ if(Util.needChangeUpAdress(ret)){
+ orginHost = Conf.UP_HOST.equals(orginHost) ? Conf.UP_HOST2 : Conf.UP_HOST;
+ }
+ if(time > 0){
+ ret = null;
+ return uploadMkblk(blkLength, chunkData, time - 1);
+ }
+ }
+ return ret;
+ }
+
+ private ChunkUploadCallRet uploadChunk(ChunkUploadCallRet ret, byte[] chunkData) {
+ String url = getBlkUrl(ret);
+ return upload(url, chunkData, Conf.CHUNK_TRY_TIMES);
+ }
+
+ private ChunkUploadCallRet upload(String url, byte[] chunkData, int time) {
+ try {
+ if(task.isCancelled()){
+ time -= (Conf.CHUNK_TRY_TIMES * 2);
+ return new ChunkUploadCallRet(Conf.CANCEL_CODE, "", Conf.PROCESS_MSG);
+ }
+ task.post = Util.newPost(url);
+ task.post.setHeader("Authorization", "UpToken " + task.auth.getUploadToken());
+ task.post.setEntity(buildHttpEntity(chunkData));
+ HttpResponse response = task.getHttpClient().execute(task.post);
+ ChunkUploadCallRet ret = new ChunkUploadCallRet(Util.handleResult(response));
+
+ return checkAndRetryUpload(url, chunkData, time, ret);
+ } catch (Exception e) {
+ int status = task.isCancelled() ? Conf.CANCEL_CODE : Conf.ERROR_CODE;
+ return new ChunkUploadCallRet(status, e);
+ }
+ }
+
+ private HttpEntity buildHttpEntity(byte[] isa) throws IOException {
+ ByteArrayEntity en = new ByteArrayEntity(isa);
+ return en;
+ }
+
+ private ChunkUploadCallRet checkAndRetryUpload(String url,
+ byte[] chunkData, int time, ChunkUploadCallRet ret) throws IOException {
+ if(!ret.isOk()){
+ if(time > 0 && needPutRetry(ret)){
+ return upload(url, chunkData, time - 1);
+ }else{
+ return ret;
+ }
+ }
+ else{
+
+ long crc32 = Crc32.calc(chunkData);
+ // 上传的数据 CRC32 校验错。
+ if(ret.getCrc32() != crc32){
+ if(time > 0){
+ return upload(url, chunkData, time - 1);
+ }else{
+ // 406 上传的数据 CRC32 校验错。
+ return new ChunkUploadCallRet(Conf.ERROR_CODE, "", "local's crc32 do not match.");
+ }
+ }else{
+ return ret;
+ }
+ }
+ }
+
+ // 701 上传数据块校验出错需要整个块重试,块重试单独判断。
+ // 406 上传的数据 CRC32 校验错;500 服务端失败; 996 ??;
+ private boolean needPutRetry(ChunkUploadCallRet ret){
+ return ret.getStatusCode() == 406 || ret.getStatusCode() == 996 || ret.getStatusCode() / 100 == 5;
+ }
+
+ private String getMkblkUrl(long blkLength) {
+ String url = orginHost + "/mkblk/" + blkLength;
+ return url;
+ }
+
+ private String getBlkUrl(ChunkUploadCallRet ret) {
+ String url = ret.getHost() + "/bput/" + ret.getCtx() + "/" + ret.getOffset();
+ return url;
+ }
+
+ }
+
+}
diff --git a/src/com/qiniu/rs/CallBack.java b/src/com/qiniu/rs/CallBack.java
new file mode 100644
index 000000000..201551db8
--- /dev/null
+++ b/src/com/qiniu/rs/CallBack.java
@@ -0,0 +1,12 @@
+package com.qiniu.rs;
+
+import com.qiniu.resumableio.SliceUploadTask.Block;
+
+public abstract class CallBack {
+
+ public abstract void onProcess(long current, long total);
+ public abstract void onSuccess(UploadCallRet ret);
+ public abstract void onFailure(CallRet ret);
+ public void onBlockSuccess(Block blk){}
+
+}
diff --git a/src/com/qiniu/rs/CallRet.java b/src/com/qiniu/rs/CallRet.java
new file mode 100644
index 000000000..8c962a1a4
--- /dev/null
+++ b/src/com/qiniu/rs/CallRet.java
@@ -0,0 +1,80 @@
+package com.qiniu.rs;
+
+
+public class CallRet {
+ private final int statusCode;
+ private final String reqId;
+ private final String response;
+ private Exception exception;
+
+ /**
+ * 子类必须实现此构造函数
+ * @param ret
+ */
+ public CallRet(CallRet ret){
+ this.statusCode = ret.statusCode;
+ this.reqId = ret.reqId;
+ this.response = ret.response;
+ this.exception = ret.exception;
+ doUnmarshal();
+ }
+
+ public CallRet(int statusCode, String reqId, String responseBody) {
+ this.statusCode = statusCode;
+ this.reqId = reqId;
+ this.response = responseBody;
+ doUnmarshal();
+ }
+
+ public CallRet(int errorCode, String reqId, Exception e) {
+ this.statusCode = errorCode;
+ this.reqId = reqId;
+ this.response = "";
+ exception = e;
+ doUnmarshal();
+ }
+
+ private void doUnmarshal() {
+ if (this.exception != null || this.response == null
+ || !this.response.trim().startsWith("{")) {
+ return;
+ }
+ try {
+ unmarshal();
+ } catch (Exception e) {
+ if (this.exception == null) {
+ this.exception = e;
+ }
+ }
+ }
+
+ protected void unmarshal() throws Exception {
+
+ }
+
+ public boolean isOk(){
+ return this.statusCode / 100 == 2;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+ public String getReqId() {
+ return reqId;
+ }
+ public String getResponse() {
+ return response;
+ }
+ public Exception getException() {
+ return exception;
+ }
+
+ public String toString(){
+ String s = "statusCode: " + statusCode + ", reqId: " + reqId + ", response: " + response;
+ if(exception != null){
+ s += ", ex: "+ exception.toString();
+ }
+ return s;
+ }
+
+}
diff --git a/src/com/qiniu/rs/ChunkUploadCallRet.java b/src/com/qiniu/rs/ChunkUploadCallRet.java
new file mode 100644
index 000000000..c920ed77a
--- /dev/null
+++ b/src/com/qiniu/rs/ChunkUploadCallRet.java
@@ -0,0 +1,62 @@
+package com.qiniu.rs;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.qiniu.rs.CallRet;
+
+public class ChunkUploadCallRet extends CallRet {
+
+ protected String ctx;
+ protected String checksum;
+ protected int offset;
+ protected String host;
+ protected long crc32;
+
+ public ChunkUploadCallRet(CallRet ret) {
+ super(ret);
+ }
+
+ public ChunkUploadCallRet(int statusCode, String reqid, String response) {
+ super(statusCode, reqid, response);
+ }
+
+ public ChunkUploadCallRet(int statusCode, Exception e) {
+ this(statusCode, "", e);
+ }
+
+ public ChunkUploadCallRet(int statusCode, String reqid, Exception e) {
+ super(statusCode, reqid, e);
+ }
+
+ @Override
+ protected void unmarshal() throws JSONException{
+ JSONObject jsonObject = new JSONObject(this.getResponse());
+ ctx = jsonObject.optString("ctx", null);
+ checksum = jsonObject.optString("checksum", null);
+ offset = jsonObject.optInt("offset", 0);
+ host = jsonObject.optString("host", null);
+ crc32 = jsonObject.optLong("crc32", 0);
+ }
+
+ public String getCtx() {
+ return ctx;
+ }
+
+ public String getChecksum() {
+ return checksum;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public long getCrc32() {
+ return crc32;
+ }
+
+}
diff --git a/src/com/qiniu/io/PutExtra.java b/src/com/qiniu/rs/PutExtra.java
similarity index 79%
rename from src/com/qiniu/io/PutExtra.java
rename to src/com/qiniu/rs/PutExtra.java
index 427bdd264..831f491bb 100644
--- a/src/com/qiniu/io/PutExtra.java
+++ b/src/com/qiniu/rs/PutExtra.java
@@ -1,4 +1,4 @@
-package com.qiniu.io;
+package com.qiniu.rs;
import java.util.HashMap;
@@ -7,7 +7,7 @@ public class PutExtra {
public final static int AUTO_CRC32 = 1;
public final static int SPECIFY_CRC32 = 2;
- public HashMap params = new HashMap(); // 用户自定义参数,key必须以 "x:" 开头
+ public HashMap params = new HashMap(); // 用户自定义参数,key必须以 "x:" 开头
public String mimeType;
public long crc32;
public int checkCrc = UNUSE_CRC32;
diff --git a/src/com/qiniu/rs/UploadCallRet.java b/src/com/qiniu/rs/UploadCallRet.java
new file mode 100644
index 000000000..e4d8b4a8d
--- /dev/null
+++ b/src/com/qiniu/rs/UploadCallRet.java
@@ -0,0 +1,43 @@
+package com.qiniu.rs;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.qiniu.rs.CallRet;
+
+public class UploadCallRet extends CallRet {
+ protected String hash;
+ protected String key;
+
+ public UploadCallRet(CallRet ret) {
+ super(ret);
+ }
+
+ public UploadCallRet(int statusCode, String reqid, String response) {
+ super(statusCode, reqid, response);
+ }
+
+ public UploadCallRet(int statusCode, Exception e) {
+ this(statusCode, "", e);
+ }
+
+ public UploadCallRet(int statusCode, String reqid, Exception e) {
+ super(statusCode, reqid, e);
+ }
+
+ @Override
+ protected void unmarshal() throws JSONException{
+ JSONObject jsonObject = new JSONObject(this.getResponse());
+ hash = jsonObject.optString("hash", null);
+ key = jsonObject.optString("key", null);
+ }
+
+ public String getHash() {
+ return hash;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+}
diff --git a/src/com/qiniu/rs/UploadTaskExecutor.java b/src/com/qiniu/rs/UploadTaskExecutor.java
new file mode 100644
index 000000000..0277cf4d4
--- /dev/null
+++ b/src/com/qiniu/rs/UploadTaskExecutor.java
@@ -0,0 +1,37 @@
+package com.qiniu.rs;
+
+import com.qiniu.utils.UploadTask;
+
+public class UploadTaskExecutor {
+ private volatile UploadTask task;
+
+ public UploadTaskExecutor() {
+
+ }
+
+ public UploadTaskExecutor(UploadTask task) {
+ this.task = task;
+ }
+
+ public void setTask(UploadTask task) {
+ this.task = task;
+ }
+
+ public CallRet get(){
+ if(task != null){
+ try {return task.get();} catch (Exception e) {}
+ }
+ return null;
+ }
+
+ public boolean isUpCancelled(){
+ return task != null && task.isUpCancelled();
+ }
+
+ public void cancel(){
+ if(task != null){
+ try{task.cancel();}catch(Exception e){}
+ }
+ }
+
+}
diff --git a/src/com/qiniu/utils/Base64.java b/src/com/qiniu/utils/Base64.java
deleted file mode 100644
index 8134fcb67..000000000
--- a/src/com/qiniu/utils/Base64.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.qiniu.utils;
-
-public class Base64 {
- public static String encode(String data) {
- return android.util.Base64.encodeToString(data.getBytes(),
- android.util.Base64.URL_SAFE | android.util.Base64.NO_WRAP);
- }
-}
diff --git a/src/com/qiniu/utils/Crc32.java b/src/com/qiniu/utils/Crc32.java
index 4657b916c..d1d275a08 100644
--- a/src/com/qiniu/utils/Crc32.java
+++ b/src/com/qiniu/utils/Crc32.java
@@ -4,8 +4,10 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
public class Crc32 {
+
public static long calc(byte[] data, int offset, int length) {
CRC32 crc32 = new CRC32();
crc32.update(data, offset, length);
@@ -15,14 +17,18 @@ public static long calc(byte[] data, int offset, int length) {
public static long calc(byte[] data) {
return calc(data, 0, data.length);
}
-
+
public static long calc(File file) throws IOException{
- FileInputStream fis = new FileInputStream(file);
+ FileInputStream fis = new FileInputStream(file);
+ return calc(fis);
+ }
+
+ public static long calc(InputStream is) throws IOException{
int blockSize = 64 * 1024;
byte[] buffer = new byte[blockSize];
int read = 0;
CRC32 crc32 = new CRC32();
- while((read = fis.read(buffer, 0, buffer.length)) > 0){
+ while((read = is.read(buffer, 0, buffer.length)) > 0){
crc32.update(buffer, 0, read);
}
return crc32.getValue();
diff --git a/src/com/qiniu/utils/FileUri.java b/src/com/qiniu/utils/FileUri.java
deleted file mode 100644
index 82934a34b..000000000
--- a/src/com/qiniu/utils/FileUri.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.qiniu.utils;
-
-import java.io.File;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-
-public class FileUri {
- public static File getFile(Context mContext, Uri uri) {
- uri = fileUri(mContext, uri);
- return new File(uri.getPath());
- }
-
- private static Uri fileUri(Context mContext, Uri uri){
- if (uri.toString().startsWith("file")){
- return uri;
- }
- String filePath;
- if (uri != null && "content".equals(uri.getScheme())) {
- Cursor cursor = mContext.getContentResolver().query(uri,
- new String[] { android.provider.MediaStore.Images.ImageColumns.DATA },
- null, null, null);
- cursor.moveToFirst();
- filePath = cursor.getString(0);
- cursor.close();
- } else {
- filePath = uri.getPath();
- }
- return Uri.parse("file://" + filePath);
- }
-
- public static File getSDPath(Context context){
- File sdDir = context.getCacheDir();
- boolean sdCardExist = Environment.getExternalStorageState()
- .equals(Environment.MEDIA_MOUNTED);
- if (sdCardExist) {
- sdDir = Environment.getExternalStorageDirectory();
- }
- return sdDir;
- }
-}
diff --git a/src/com/qiniu/utils/Http.java b/src/com/qiniu/utils/Http.java
new file mode 100644
index 000000000..53c5cc56d
--- /dev/null
+++ b/src/com/qiniu/utils/Http.java
@@ -0,0 +1,55 @@
+package com.qiniu.utils;
+
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnManagerParams;
+import org.apache.http.conn.params.ConnPerRoute;
+import org.apache.http.conn.params.ConnPerRouteBean;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+
+import com.qiniu.conf.Conf;
+
+public class Http {
+ private static HttpClient httpClient;
+
+ public static HttpClient getHttpClient() {
+ if (httpClient == null) {
+ httpClient = buildHttpClient();
+ }
+ return httpClient;
+ }
+
+ private static HttpClient buildHttpClient() {
+ HttpParams httpParams = new BasicHttpParams();
+ ConnManagerParams.setMaxTotalConnections(httpParams, 10);
+ ConnPerRoute connPerRoute = new ConnPerRouteBean(3);
+ ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);
+ HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
+
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", PlainSocketFactory
+ .getSocketFactory(), 80));
+ registry.register(new Scheme("https", SSLSocketFactory
+ .getSocketFactory(), 443));
+ ClientConnectionManager cm = new ThreadSafeClientConnManager(
+ httpParams, registry);
+
+ HttpClient httpClient = new DefaultHttpClient(cm, httpParams);
+
+ httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, Conf.SO_TIMEOUT);
+ httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, Conf.CONNECTION_TIMEOUT);
+
+ return httpClient;
+ }
+
+}
diff --git a/src/com/qiniu/utils/ICancel.java b/src/com/qiniu/utils/ICancel.java
deleted file mode 100644
index 96e979ae2..000000000
--- a/src/com/qiniu/utils/ICancel.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.qiniu.utils;
-
-public interface ICancel {
- public boolean cancel(boolean isIntercupt);
-}
diff --git a/src/com/qiniu/utils/IOnProcess.java b/src/com/qiniu/utils/IOnProcess.java
index c0ac0fb79..6c02788aa 100644
--- a/src/com/qiniu/utils/IOnProcess.java
+++ b/src/com/qiniu/utils/IOnProcess.java
@@ -1,6 +1,6 @@
package com.qiniu.utils;
public interface IOnProcess {
+
public void onProcess(long current, long total);
- public void onFailure(QiniuException ex);
}
diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java
index 2272b9ead..2f16636a0 100644
--- a/src/com/qiniu/utils/InputStreamAt.java
+++ b/src/com/qiniu/utils/InputStreamAt.java
@@ -1,263 +1,477 @@
package com.qiniu.utils;
+import android.content.ContentResolver;
import android.content.Context;
-import android.os.Environment;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.entity.AbstractHttpEntity;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
import java.io.*;
+import java.util.ArrayList;
-import com.qiniu.auth.Client;
-
-public class InputStreamAt implements Closeable {
- private RandomAccessFile mFileStream;
- private byte[] mData;
+public abstract class InputStreamAt implements Closeable {
- private File mFile;
- private boolean mClosed;
- private boolean mDelWhenClose = false;
- private long mCrc32 = -1;
- private long mLength = -1;
+ public static UriInput fromUri(Context context, Uri uri){
+ return new UriInput(context, uri);
+ }
+
+ public static FileInput fromInputStream(Context context, InputStream is) throws IOException {
+ return fromInputStream(context, is, null);
+ }
- /**
- * @param context
- * @param is InputStream
- */
- public static InputStreamAt fromInputStream(Context context, InputStream is) {
- File file = storeToFile(context, is);
+ public static FileInput fromInputStream(Context context, InputStream is, String filename) throws IOException {
+ final File file = Util.storeToFile(context, is);
if (file == null) {
return null;
}
- InputStreamAt isa = new InputStreamAt(file, true);
- return isa;
- }
+
+ FileInput isa = null;
+ try{
+ isa = new FileInput(file, filename);
+ isa.cleans.add(new CleanCallBack(){
+
+ @Override
+ public void clean() {
+ file.delete();
+ }
+
+ });
+ }catch(IOException e){
+ if(file != null){
+ file.delete();
+ }
+ throw e;
+ }
- public static InputStreamAt fromFile(File f) {
- InputStreamAt isa = new InputStreamAt(f);
return isa;
}
- public static InputStreamAt fromString(String str) {
- return new InputStreamAt(str.getBytes());
+ public static FileInput fromFile(File f) throws FileNotFoundException {
+ return new FileInput(f);
}
+
+ public static ByteInput fromByte(byte[] data){
+ return new ByteInput(data);
+ }
+
+ protected static Input buildNextInput(final InputStreamAt isa, final int len){
+ return new Input(){
+ private final long start = isa.getOffset();
+ private int innerOffset = 0;
+ @Override
+ public byte[] readAll() throws IOException {
+ return isa.read(start, len);
+ }
- public InputStreamAt(File file) {
- this(file, false);
+ @Override
+ public byte[] readNext(int length) throws IOException {
+ if(innerOffset + length > len){
+ length = len - innerOffset;
+ }
+ if(length <= 0){
+ return new byte[0];
+ }
+ byte[] bs = isa.read(start + innerOffset, length);
+ innerOffset += length;
+ return bs;
+ }
+
+ };
}
+
+ protected long outterOffset;
+
+ protected ArrayList cleans = new ArrayList();
- public InputStreamAt(File file, boolean delWhenClose) {
- mFile = file;
- mDelWhenClose = delWhenClose;
- try {
- mFileStream = new RandomAccessFile(mFile, "r");
- mLength = mFileStream.length();
- } catch (IOException e) {
- e.printStackTrace();
- }
+ public abstract long crc32() throws IOException;
+
+ public abstract long length();
+
+ public String getFilename(){
+ return null;
}
+
+ public Input readNext(int len) throws IOException{
+ if(len + outterOffset >= length()){
+ len = (int)(length() - outterOffset);
+ }
- public InputStreamAt(byte[] data) {
- mData = data;
- mLength = mData.length;
+ Input input = buildNextInput(this, len);
+ outterOffset += len;
+ return input;
}
- public long partCrc32(long offset, int length) throws IOException {
- byte[] data = read(offset, length);
- return Crc32.calc(data);
+ public void reset() throws IOException{
+ outterOffset = 0;
+ }
+
+ protected long getOffset(){
+ return outterOffset;
}
+
+ protected abstract byte[] read(long offset, int length) throws IOException;
- public long crc32() throws IOException {
- if (mCrc32 >= 0) {
- return mCrc32;
- }
- if (mData != null) {
- mCrc32 = Crc32.calc(mData);
- return mCrc32;
- }
- if (mFile != null) {
- mCrc32 = Crc32.calc(mFile);
- return mCrc32;
+ public void close(){
+ try{doClose();}catch(Exception e){}
+ for(CleanCallBack clean : cleans){
+ try{clean.clean();}catch(Exception e){}
}
- return mCrc32;
}
+
+ protected abstract void doClose();
- public long length() {
- return mLength;
+ public static interface CleanCallBack{
+ void clean();
}
-
- public byte[] read(long offset, int length) throws IOException {
- if (mClosed) {
- throw new IOException("inputStreamAt closed");
+
+ public static interface Input{
+ byte[] readAll() throws IOException;
+ byte[] readNext(int length) throws IOException;
+ }
+
+ public static class ByteInput extends InputStreamAt{
+ private final byte[] data;
+
+ public ByteInput(byte[] data){
+ this.data = data;
+ }
+
+ public long length(){
+ return data.length;
}
- if (mFileStream != null) {
- return fileStreamRead(offset, length);
+
+ public long crc32() throws IOException {
+ return Crc32.calc(data);
}
- if (mData != null) {
- byte[] ret = new byte[length];
- System.arraycopy(mData, (int) offset, ret, 0, length);
- return ret;
+
+ public void doClose(){
+
+ }
+
+ protected byte[] read(long off, int len) {
+ int offset = (int) off;
+ if(offset == 0 && len == length()){
+ return data;
+ }
+ if(len <= 0){
+ return new byte[0];
+ }
+ byte[] bs = new byte[len];
+ System.arraycopy(data, offset, bs, 0, len);
+ offset += len;
+ return bs;
}
- throw new IOException("inputStreamAt not init");
+
}
-
- @Override
- public synchronized void close(){
- if (mClosed){
- return;
+
+ public static class FileInput extends InputStreamAt{
+ private final RandomAccessFile randomAccessFile;
+ private final File file;
+ private final String filename;
+
+ public FileInput(File file) throws FileNotFoundException{
+ this(file, null);
+ }
+
+ public FileInput(File file, String aliasFilename) throws FileNotFoundException{
+ this.file = file;
+ this.randomAccessFile = new RandomAccessFile(file, "r");
+ this.filename = (aliasFilename != null && aliasFilename.trim().length() > 0) ? aliasFilename : file.getName();
}
- mClosed = true;
+
- if (mFileStream != null) {
- try {
- mFileStream.close();
- } catch (IOException e) {
- e.printStackTrace();
+ public long length(){
+ return file.length();
+ }
+
+ public String getFilename(){
+ return filename;
+ }
+
+ public long crc32() throws IOException {
+ return Crc32.calc(file);
+ }
+
+ public void doClose(){
+ if(randomAccessFile !=null){
+ try {randomAccessFile.close();} catch (IOException e) {}
}
}
-
- if (mFile != null && mDelWhenClose) {
- mFile.delete();
+
+ protected byte[] read(long offset, int len) throws IOException {
+ if(offset >= length()){
+ return null;
+ }
+ byte[] bs = new byte[len];
+ randomAccessFile.seek(offset);
+ randomAccessFile.read(bs);
+ offset += len;
+ return bs;
}
}
+
+ public static class UriInput extends InputStreamAt{
+ private UriInfo uriInfo;
+ private FileInput fileInput;
+ private InputStream is;
+
+ public UriInput(Context context, Uri uri){
+ uriInfo = new UriInfo(context, uri);
+ File f = uriInfo.getFile();
+ if(f != null && f.exists() && f.isFile()){
+ try {
+ fileInput = new FileInput(f);
+ } catch (FileNotFoundException e) {
- public int read(byte[] data) throws IOException {
- return mFileStream.read(data);
- }
-
- public void reset(){
- if (mClosed) {
- return;
- }
- if (mFileStream != null) {
- try{
- mFileStream.seek(0);
- }catch(IOException e){
- e.printStackTrace();
+ }
}
}
- }
-
- public HttpEntity toHttpEntity(final long offset, final int length, final Client.ClientExecutor client) {
- final InputStreamAt input = this;
- return new AbstractHttpEntity() {
- @Override
- public boolean isRepeatable() {
- return false;
+
+ private void genIs() throws FileNotFoundException{
+ if(fileInput == null && is == null && uriInfo != null){
+ is = uriInfo.getIs();
}
-
- @Override
- public long getContentLength() {
- return length;
+ }
+
+ public long length(){
+ return uriInfo.length();
+ }
+
+ public String getFilename(){
+ return uriInfo.getName();
+ }
+
+ public long crc32() throws IOException {
+ if(fileInput != null){
+ return fileInput.crc32();
+ }else{
+ UriInfo info = new UriInfo(uriInfo.getContext(), uriInfo.getUri());
+ return Crc32.calc(info.getIs());
+ }
+ }
+
+ public void reset() throws IOException{
+ super.reset();
+ if(fileInput != null){
+ fileInput.reset();
+ }else if(uriInfo != null){
+ uriInfo.reset();
+ outterOffset = 0;
+ is = uriInfo.getIs();
+ }
+ }
+
+ public void doClose(){
+ if(is != null){
+ try {is.close();} catch (IOException e) {}
}
+ if(uriInfo != null){
+ uriInfo.close();
+ }
+ if(fileInput != null){
+ fileInput.close();
+ }
+
+ is = null;
+ uriInfo = null;
+ fileInput = null;
+ }
+
+
+ public Input readNext(final int len) throws IOException {
+ if(fileInput != null){
+ return fileInput.readNext(len);
+ }else{
+ // 流不支持随机读取,先读取整块byte[],再从其中读取部分
+ return new Input(){
+ private byte[] content;
+ private ByteInput bi;
+
+ //保证 流 被正常消耗
+ {
+ content = readNextContent(len);
+ bi = new ByteInput(content);
+ }
+
+ @Override
+ public byte[] readAll() throws IOException {
+ return content;
+ }
- @Override
- public InputStream getContent() throws IOException, IllegalStateException {
+ @Override
+ public byte[] readNext(int length) throws IOException {
+ return bi.readNext(length).readAll();
+ }
+
+ };
+ }
+ }
+
+ private byte[] readNextContent(int l) throws IOException {
+ if(outterOffset >= length()){
return null;
}
+ genIs();
+ int len = l;
+ if(len + outterOffset >= length()){
+ len = (int)(length() - outterOffset);
+ }
+ byte[] bs = new byte[len];
+ is.read(bs);
+ outterOffset += len;
+ return bs;
+ }
+
+
+ protected byte[] read(long offset, int len) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public static class UriInfo{
+ private final Context context;
+
+ private final Uri uri;
+
+ private InputStream is;
+ private File file;
+ private String name;
+ private String mimeType;
+ private String path;
+ private long length = -1;
+
+ /**
+ * 通过uri查找文件,若找到,构建基于文件的 InputStreamIsa ,委托七处理;
+ * 否则获取流
+ * @throws FileNotFoundException
+ */
+ UriInfo(Context context, Uri uri){
+ this.context = context;
+ this.uri = uri;
+ build();
+ }
+
- @Override
- public void writeTo(OutputStream outputStream) throws IOException {
- int blockSize = 64 * 1024;
- long start = offset;
- long initStart = 0;
- long end = offset + length;
- long total = end - start;
- while (start < end) {
- if (mClosed) {
- outputStream.close();
+ private void build() {
+ tryContentFile(uri.getPath());
+ tryContentField();
+ if(hasFile()){
+ return;
+ }
+
+ checkContent();
+
+ tryContentFile(path);
+ if(hasFile()){
+ return;
+ }
+ }
+
+
+
+ public void reset() throws IOException{
+ close();
+ genContentIs();
+ }
+
+ private boolean hasFile(){
+ return file != null && file.exists() && file.isFile();
+ }
+
+
+ private void tryContentFile(String path){
+ if(path != null){
+ String[] ps = {"", "/mnt", "/mnt/"};
+ for(String p : ps){
+ try{file = new File(p + path);}catch(Exception e){}
+ if(hasFile()){
return;
}
- int readLength = (int) StrictMath.min((long) blockSize, end-start);
- byte[] data = input.read(start, readLength);
- outputStream.write(data);
- outputStream.flush();
- initStart += readLength;
- client.upload(initStart, total);
- start += readLength;
}
}
-
- @Override
- public boolean isStreaming() {
- return false;
+ }
+
+ private void tryContentField(){
+ if(hasFile()){
+ name = file.getName();
+ length = file.length();
+ path = file.getAbsolutePath();
}
- };
- }
-
- private static byte[] copyOfRange(byte[] original, int from, int to) {
- int newLength = to - from;
- if (newLength < 0) {
- throw new IllegalArgumentException(from + " > " + to);
- }
- byte[] copy = new byte[newLength];
- System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
- return copy;
- }
-
+ }
+
+ private void genContentIs() throws FileNotFoundException{
+ is = context.getContentResolver().openInputStream(uri);
+ }
+
+ private void checkContent(){
+ if ("content".equalsIgnoreCase(uri.getScheme())){
+ Cursor cursor = null;
+ try{
+ ContentResolver resolver = context.getContentResolver();
+ String [] col = {MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME,
+ MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.DATA};
+ cursor = resolver.query(uri, col, null, null, null);
+ if(cursor != null && cursor.moveToFirst()){
+ int cc = cursor.getColumnCount();
+ for(int i=0; i < cc; i++){
+ String colName = cursor.getColumnName(i);
+ String colValue = cursor.getString(i);
+ if(MediaStore.MediaColumns.DISPLAY_NAME.equalsIgnoreCase(colName)){
+ name = colValue;
+ }else if(MediaStore.MediaColumns.SIZE.equalsIgnoreCase(colName)){
+ length = cursor.getLong(i);
+ }else if(MediaStore.MediaColumns.MIME_TYPE.equalsIgnoreCase(colName)){
+ mimeType = colValue;
+ }else if(MediaStore.MediaColumns.DATA.equalsIgnoreCase(colName)){
+ path = colValue;
+ }
+ }
+ }
+ }finally{
+ if(cursor != null){
+ try{cursor.close();}catch(Exception e){}
+ }
+ }
+ }
- private static File storeToFile(Context context, InputStream is) {
- if (is == null) {
- return null;
}
- OutputStream os = null;
- File f = null;
- boolean failed = false;
- try {
- File outputDir = FileUri.getSDPath(context);
- f = File.createTempFile("qiniu-", "", outputDir);
- os = new FileOutputStream(f);
- byte[] buffer = new byte[64 * 1024];
- int bytesRead;
- while ((bytesRead = is.read(buffer)) != -1) {
- os.write(buffer, 0, bytesRead);
- }
- } catch (IOException e) {
- e.printStackTrace();
- failed = true;
- }
- try {
- is.close();
- } catch (IOException e){
- e.printStackTrace();
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException e){
- e.printStackTrace();
+
+ public void close(){
+ if(is != null){
+ try {is.close();} catch (Exception e) {}
}
+ is = null;
}
-
-
- if (failed && f != null) {
- f.delete();
- f = null;
+
+ public Context getContext() {
+ return context;
}
- return f;
- }
- private byte[] fileStreamRead(long offset, int length) throws IOException {
- if (mFileStream == null) return null;
- long fileLength = mFileStream.length();
- if (length + offset > fileLength) length = (int) (fileLength - offset);
- byte[] data = new byte[length];
+ public Uri getUri() {
+ return uri;
+ }
- int read;
- int totalRead = 0;
- synchronized (data) {
- mFileStream.seek(offset);
- do {
- read = mFileStream.read(data, totalRead, length - totalRead);
- if (read <= 0) break;
- totalRead += read;
- } while (length > totalRead);
+
+ public File getFile(){
+ return file;
+ }
+
+ public InputStream getIs() throws FileNotFoundException{
+ if(is == null){
+ genContentIs();
+ }
+ return is;
+ }
+
+
+ public String getName() {
+ return name;
}
- if (totalRead != data.length) {
- data = copyOfRange(data, 0, totalRead);
+ public long length() {
+ return length;
}
- return data;
+
}
}
diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java
index adbed1923..10dc9f3bf 100644
--- a/src/com/qiniu/utils/MultipartEntity.java
+++ b/src/com/qiniu/utils/MultipartEntity.java
@@ -1,164 +1,334 @@
package com.qiniu.utils;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.message.BasicHeader;
-
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Random;
-import java.util.concurrent.*;
-public class MultipartEntity extends AbstractHttpEntity {
- private String mBoundary;
- private long mContentLength = -1;
- private long writed = 0;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.util.ByteArrayBuffer;
+
+import com.qiniu.conf.Conf;
+
+public class MultipartEntity extends AbstractHttpEntity {
+
+ private final Multipart mpart;
private IOnProcess mNotify;
- private StringBuffer mData = new StringBuffer();
- private ArrayList mFiles = new ArrayList();
+ private volatile long writed = 0;
+ private volatile long length = -1;
public MultipartEntity() {
- mBoundary = getRandomString(32);
- contentType = new BasicHeader("Content-Type", "multipart/form-data; boundary=" + mBoundary);
+ mpart = new Multipart();
+ String boundary = mpart.getBoundary();
+ contentType = new BasicHeader("Content-Type",
+ "multipart/form-data; boundary=" + boundary);
}
public void addField(String key, String value) {
- String tmp = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n";
- mData.append(String.format(tmp, mBoundary, key, value));
+ mpart.add(new StringFormPart(key, value));
}
- public void addFile(String field, String contentType, String fileName, InputStreamAt isa) {
- mFiles.add(new FileInfo(field, contentType, fileName, isa));
+ public void addFile(String field, String contentType, String fileName,
+ InputStreamAt isa) {
+ mpart.add(new FileInfo(field, contentType, fileName, isa));
+ }
+
+ public void setProcessNotify(IOnProcess ret) {
+ mNotify = ret;
}
@Override
- public boolean isRepeatable() {
- return true;
+ public InputStream getContent() throws IOException, IllegalStateException {
+ return null;
}
@Override
public long getContentLength() {
- if (mContentLength > 0) return mContentLength;
- long len = mData.toString().getBytes().length;
- for (FileInfo fi: mFiles) {
- len += fi.length();
- }
- len += 6 + mBoundary.length();
- mContentLength = len;
- return len;
+ if(length < 0){
+ length = mpart.getTotalLength();
+ }
+ return length;
}
@Override
- public InputStream getContent() throws IOException, IllegalStateException {
- return null;
+ public boolean isRepeatable() {
+ return true;
}
@Override
- public void writeTo(OutputStream outputStream) throws IOException {
- writed = 0;
- outputStream.write(mData.toString().getBytes());
- outputStream.flush();
- writed += mData.toString().getBytes().length;
- if (mNotify != null) mNotify.onProcess(writed, getContentLength());
- for (FileInfo i: mFiles) {
- i.writeTo(outputStream);
- }
- byte[] data = ("--" + mBoundary + "--\r\n").getBytes();
- outputStream.write(data);
- outputStream.flush();
- writed += data.length;
- if (mNotify != null) mNotify.onProcess(writed, getContentLength());
- outputStream.close();
+ public boolean isStreaming() {
+ return false;
}
@Override
- public boolean isStreaming() {
- return false;
+ public void writeTo(OutputStream out) throws IOException {
+ mpart.writeTo(out);
}
+
+ static class Multipart {
+ private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ .toCharArray();
- private String fileTpl = "--%s\r\nContent-Disposition: form-data;name=\"%s\";filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
+ private static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII");
+ private static final ByteArrayBuffer CR_LF = encode(DEFAULT_CHARSET, "\r\n");
+ private static final ByteArrayBuffer TWO_DASHES = encode(DEFAULT_CHARSET, "--");
+ private static final ByteArrayBuffer CONTENT_DISP = encode(
+ DEFAULT_CHARSET, "Content-Disposition: form-data");
- public void setProcessNotify(IOnProcess ret) {
- mNotify = ret;
- }
- ExecutorService executor = Executors.newFixedThreadPool(1);
-
- class FileInfo {
-
- public String mField = "";
- public String mContentType = "";
- public String mFilename = "";
- public InputStreamAt mIsa;
-
- public FileInfo(String field, String contentType, String filename, InputStreamAt isa) {
- mField = field;
- mContentType = contentType;
- mFilename = filename;
- mIsa = isa;
- if (mContentType == null || mContentType.length() == 0) {
- mContentType = "application/octet-stream";
+ private static ByteArrayBuffer encode(final Charset charset,
+ final String string) {
+ ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
+ ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
+ bab.append(encoded.array(), encoded.position(), encoded.remaining());
+ return bab;
+ }
+
+ private static ByteArrayBuffer encode(final String string) {
+ final Charset charset = Charset.forName(Conf.CHARSET);
+ return encode(charset, string);
+ }
+
+ private static void writeBytes(final ByteArrayBuffer b,
+ final OutputStream out) throws IOException {
+ out.write(b.buffer(), 0, b.length());
+ }
+
+
+ private final ArrayList parts;
+ private final String boundary;
+
+ public Multipart() {
+ this.parts = new ArrayList();
+ this.boundary = generateBoundary();
+ }
+
+ public void add(FormPart part) {
+ parts.add(part);
+ }
+
+ public String getBoundary() {
+ return boundary;
+ }
+
+ public long getTotalLength() {
+ long contentLen = 0;
+ for (FormPart part : this.parts) {
+ long len = part.getContentLength();
+ if (len >= 0) {
+ contentLen += len;
+ } else {
+ return -1;
+ }
}
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ doWriteTo(out, false);
+ byte[] extra = out.toByteArray();
+ return contentLen + extra.length;
+ } catch (IOException ex) {
+ // Should never happen
+ return -1;
+ }
+ }
+
+ public void writeTo(final OutputStream out)throws IOException{
+ doWriteTo(out, true);
}
- public long length() {
- return fileTpl.length() - 2*4 + mBoundary.length() + mIsa.length() + 2 +
- mField.getBytes().length + mContentType.length() + mFilename.getBytes().length;
- }
-
- public void writeTo(OutputStream outputStream) throws IOException {
- byte[] data = String.format(fileTpl, mBoundary, mField, mFilename, mContentType).getBytes();
- outputStream.write(data);
- outputStream.flush();
- writed += data.length;
- if (mNotify != null) mNotify.onProcess(writed, getContentLength());
-
- int blockSize = (int) (getContentLength() / 100);
- if (blockSize > 256 * 1024) blockSize = 256 * 1024;
- if (blockSize < 32 * 1024) blockSize = 32 * 1024;
- long index = 0;
- long length = mIsa.length();
- while (index < length) {
- int readLength = (int) StrictMath.min((long) blockSize, mIsa.length() - index);
- int timeout = readLength * 2;
- try {
- write(timeout, outputStream, mIsa.read(index, readLength));
- } catch (Exception e) {
- mNotify.onFailure(QiniuException.common("multipart", e));
- return;
+ private void doWriteTo(final OutputStream out, final boolean writeContent)
+ throws IOException {
+ ByteArrayBuffer boundaryByte = encode(DEFAULT_CHARSET, boundary);
+
+ for (FormPart f : parts) {
+ writeStart(out, boundaryByte);
+ writeBytes(encode(generateName(f.getName())), out);
+ if (f.getFilename() != null) {
+ writeBytes(encode(generateFilename(f.getFilename())), out);
+ writeBytes(CR_LF, out);
+ writeBytes(encode(generateContentType(f.getContentType())),
+ out);
}
- index += blockSize;
- outputStream.flush();
- writed += readLength;
- if (mNotify != null) mNotify.onProcess(writed, getContentLength());
+ writeBytes(CR_LF, out);
+ writeBytes(CR_LF, out);
+ if (writeContent) {
+ f.writeTo(out);
+ }
+ writeBytes(CR_LF, out);
+ }
+ writeAllEnd(out, boundaryByte);
+ }
+
+ private void writeStart(final OutputStream out, ByteArrayBuffer boundaryByte) throws IOException{
+ // 开始
+ writeBytes(TWO_DASHES, out);
+ writeBytes(boundaryByte, out);
+ writeBytes(CR_LF, out);
+ writeBytes(CONTENT_DISP, out);
+ }
+
+ private void writeAllEnd(final OutputStream out, ByteArrayBuffer boundaryByte) throws IOException{
+ // 结束
+ writeBytes(TWO_DASHES, out);
+ writeBytes(boundaryByte, out);
+ writeBytes(TWO_DASHES, out);
+ writeBytes(CR_LF, out);
+ }
+
+ protected String generateName(String name) {
+ String s = "; name=\"" + name + "\"";
+ return s;
+ }
+
+ private String generateFilename(String filename) {
+ String s = "; filename=\"" + filename + "\"";
+ return s;
+ }
+
+ private String generateContentType(String contentType) {
+ String s = "Content-Type: " + contentType;
+ return s;
+ }
+
+ protected String generateBoundary() {
+ StringBuilder buffer = new StringBuilder();
+ Random rand = new Random();
+ int count = rand.nextInt(11) + 30; // a random size from 30 to 40
+ for (int i = 0; i < count; i++) {
+ buffer.append(MULTIPART_CHARS[rand
+ .nextInt(MULTIPART_CHARS.length)]);
}
- outputStream.write("\r\n".getBytes());
- outputStream.flush();
- writed += 2;
- if (mNotify != null) mNotify.onProcess(writed, getContentLength());
+ return buffer.toString();
}
}
- private void write(int timeout, final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException {
- Callable