/*
Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface)
This program is distributed under the terms of the GNU General Public License.
This file is part of xFace.
xFace is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
xFace is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with xFace. If not, see <http://www.gnu.org/licenses/>.
*/
package com.polyvi.xface.extension.advancedfiletransfer;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import org.json.JSONException;
import org.json.JSONObject;
import com.polyvi.xface.extension.XCallbackContext;
import com.polyvi.xface.extension.XExtensionContext;
import com.polyvi.xface.extension.XExtensionResult;
import com.polyvi.xface.extension.XExtensionResult.Status;
import com.polyvi.xface.plugin.api.XIWebContext;
import com.polyvi.xface.util.XLog;
import com.polyvi.xface.util.XPathResolver;
import com.polyvi.xface.util.XStringUtils;
//上传请求发起后,组装头部信息发给服务器(数据格式为:Content-Length=?;filename=?;sourceid= ?如果用户初次上传文件,sourceid的值为空).
// 服务器根据头部信息判断要上传的文件是否有上传记录,然后返回相应的信息(格式为: sourceid=?;position=?),从而根据position实现断点上传。
public class XFileUploader implements XIFileTransfer, XIFileTransferListener {
private static final String CLASS_NAME = XFileUploader.class.getSimpleName();
/**握手阶段的头文字名*/
private static final String ACTION_NAME_HAND = "HAND";
/**上传阶段的头文字名*/
private static final String ACTION_NAME_UPLOAD = "UPLOAD";
/** http头 */
private static final String HTTP_HEAD = "http://";
/** 连接超时时间 */
private static final int mTimeout = 5000;
/**读取数据的*/
private static final int FILE_NOT_FOUND_ERR = 1;
private static final int INVALID_URL_ERR = 2;
private static final int CONNECTION_ERR = 3;
/** 定义三种上传的状态:初始化状态,正在上传状态,暂停状态 */
private static final int INIT = 1;
private static final int UPLOADING = 2;
private static final int PAUSE = 3;
private int mState = INIT;
/**定义上传文件的划分倍数和单位文件大小*/
private static final int DIVIDE_SIZE_TWO = 2;
private static final int DIVIDE_SIZE_TEN = 10;
private static final int DIVIDE_SIZE_TWENTY = 20;
private static final int SIZE_KB = 1024;
/**标示读取文件结束*/
private static final int READ_FILE_END = -1;
/**标示返回值错误*/
private static final int RESULT_CODE_ERROR = -1;
/**标示服务器返回的结果码,1表示服务器成功接收到文件一个分块,0表示服务器成功接收到整个文件*/
private static final int RESULT_CODE_CHUNK_RECEIVED = 1;
private static final int RESULT_CODE_FILE_RECEIVED = 0;
/**要上传文件的大小*/
private long mUploadFileSize;
/**每次上传文件块的大小*/
private int mUploadFileSizePerTime;
/** 要上传的文件路径 */
private String mFilePath;
/**要上传的文件*/
private File mUploadFile;
/**要上传文件的服务器响应id*/
private String mResponseId;
/**要上传文件的开始位置*/
private int mStartedPosition;
/**要上传文件的已经上传的大小*/
private int mAlreadyUploadLength;
/** 服务器地址 */
private String mServer;
/** native端js回调的上下文环境 */
private XCallbackContext mCallbackCtx;
/** 当前的应用 */
private XIWebContext mWebContext;
/** 操作配置文件的对象 */
private XFileTransferRecorder mFileTransferRecorder;
/** 上传管理器 */
private XFileTransferManager mFileTransferManager;
public XFileUploader(String filePath, String server,
XExtensionContext extensionContext, XIWebContext webContext,
XFileTransferRecorder recorder, XFileTransferManager manager) {
init(filePath, server, extensionContext, webContext, recorder, manager);
}
/** 初始化方法 */
private void init(String filePath, String server,
XExtensionContext extensionContext, XIWebContext webContext,
XFileTransferRecorder recorder, XFileTransferManager manager) {
mFilePath = filePath;
mServer = server;
mWebContext = webContext;
mFileTransferRecorder = recorder;
mFileTransferManager = manager;
}
@Override
public void onSuccess() {
mFileTransferRecorder.deleteUploadInfo(mFilePath);
setState(INIT);
mFileTransferManager.removeFileTranferTask(mWebContext.getApplication().getAppId(), mFilePath);
mCallbackCtx.success();
}
@Override
public void onError(int errorCode) {
setState(INIT);
JSONObject error = new JSONObject();
Status status = Status.ERROR;
try {
error.put("code", errorCode);
error.put("source", mFilePath);
error.put("target", mServer);
} catch (JSONException e) {
status = Status.JSON_EXCEPTION;
XLog.e(CLASS_NAME, e.getMessage());
}
XExtensionResult result = new XExtensionResult(status, error);
mCallbackCtx.sendExtensionResult(result);
}
// TODO:下面代码以后会调整
@Override
public void onProgressUpdated(int completeSize, long totalSize) {
JSONObject jsonObj = new JSONObject();
Status status = Status.PROGRESS_CHANGING;
try {
jsonObj.put("loaded", completeSize);
jsonObj.put("total", totalSize);
} catch (JSONException e) {
status = Status.JSON_EXCEPTION;
XLog.e(CLASS_NAME, e.getMessage());
}
XExtensionResult result = new XExtensionResult(status, jsonObj);
result.setKeepCallback(true);
mCallbackCtx.sendExtensionResult(result);
}
/** 上传线程执行的上传函数 */
private void upload() {
// 检查路径和URL错误
if (!initUploadFileInfo()) {
return;
}
byte[] buffer = new byte[mUploadFileSizePerTime];
//分多次连接是因为一次向流写入太多数据会导致程序崩溃
while ((mAlreadyUploadLength < mUploadFileSize)) {
//握手过程
if (!handleShake()) {
break;
}
// 从文件中读取数据
int len = readFileData(buffer);
//上传文件
if (!uploadData(buffer,len)) {
break;
}
}
if (mState != PAUSE && mAlreadyUploadLength != mUploadFileSize) {
onError(CONNECTION_ERR);
}
}
/**
* 握手过程,交换头文字信息,并从服务器获取资源id和上传的开始位置
*
* @return true:握手过程成功,false:握手过程失败。
*/
private boolean handleShake() {
HttpURLConnection httpConnection = null;
DataInputStream dataInputStream = null;
String souceid = mFileTransferRecorder.getSourceId(mFilePath,"" + mUploadFileSize);
try {
httpConnection = getHttpConnection(mServer);
// 设置握手阶段的头文字
httpConnection.setRequestProperty("Charset", "UTF-8");
httpConnection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
httpConnection.setRequestProperty("ACTIONNAME", ACTION_NAME_HAND);
httpConnection.setRequestProperty("RESOURCEID", souceid);
httpConnection.setRequestProperty("FILENAME", getUploadFileName());
httpConnection.setRequestProperty("FILESIZE", "" + mUploadFileSize);
if(HttpURLConnection.HTTP_OK == httpConnection.getResponseCode()){
// 获取服务器返回过来的信息,格式为: RESOURCEID=?;BFFORE=?
dataInputStream = new DataInputStream(
httpConnection.getInputStream());
// 处理服务器传过来的response信息
handleResponse(dataInputStream.readLine());
// 如果souceid为空则表示服务器上没有存在该上传,需设置。
setSourceId(souceid);
} else {
onError(INVALID_URL_ERR);
}
} catch (Exception e) {
onError(INVALID_URL_ERR);
e.printStackTrace();
return false;
} finally {
if (null != httpConnection) {
httpConnection.disconnect();
httpConnection = null;
}
// 握手过程结束,关闭连接。
try {
if(null != dataInputStream) {
dataInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 文件上传过程
*
* @return true:本次上传操作成功,false:本次上传操作失败。
*/
private boolean uploadData(byte[] buffer, int len) {
HttpURLConnection httpConnection = null;
InputStream inStream = null;
try {
// 设置上传阶段的头文字
httpConnection = getHttpConnection(mServer);
httpConnection.setRequestProperty("Charset", "UTF-8");
httpConnection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
httpConnection.setRequestProperty("ACTIONNAME", ACTION_NAME_UPLOAD);
httpConnection.setRequestProperty("RESOURCEID", mResponseId);
httpConnection.setRequestProperty("BEFORE", "" + mStartedPosition);
// 向服务器写入buffer中数据
writeBufferData(httpConnection.getOutputStream(), buffer, len);
// 获取从服务器传来的结果码
int resultCode = getResultCode(httpConnection);
doProcessUpdate(resultCode, len);
if ((RESULT_CODE_FILE_RECEIVED == resultCode)
&& (mAlreadyUploadLength == mUploadFileSize)) {
onSuccess();
}
if (mState == PAUSE) {
return false;
}
} catch (Exception e) {
onError(CONNECTION_ERR);
XLog.e(CLASS_NAME, "connection error");
return false;
} finally {
if (null != inStream) {
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != httpConnection) {
httpConnection.disconnect();
httpConnection = null;
}
}
return true;
}
/**
* 初始化上传文件信息
*
* @return true:初始化上传文件信息成功,false:始化上传文件信息失败。
* */
private boolean initUploadFileInfo() {
// 检查路径和URL错误
if (null == (mUploadFile = getFile())) {
onError(FILE_NOT_FOUND_ERR);
return false;
}
if (!mServer.startsWith(HTTP_HEAD)) {
onError(INVALID_URL_ERR);
return false;
}
mUploadFileSize = getUploadFileSize();
mUploadFileSizePerTime = getSingleUploadLength();
return true;
}
/**
* 处理服务器的返回的头文件信息
*
* @param response
* :待处理的response
* @return true:response结果正确,false:response结果错误。
* */
private void handleResponse(String response) throws Exception {
if (XStringUtils.isEmptyString(response)) {
throw new Exception("response is null");
}
String items[] = response.split(";");
int first_index = items[0].indexOf(":");
int second_index = items[1].indexOf(":");
if ((RESULT_CODE_ERROR == first_index) || (RESULT_CODE_ERROR == second_index)) {
throw new Exception("response error");
}
mResponseId = items[0].substring(items[0].indexOf(":") + 1);
mStartedPosition = Integer.parseInt(items[1].substring(items[1].indexOf(":") + 1));
if(mStartedPosition > mUploadFileSize)
{
throw new Exception("file StartedPosition is bigger than fileSize error");
}
}
/**
* 如果souceid为空则表示服务器上没有存在该上传记录
*
* @param souceid
* :获取到得sourceid
*/
private void setSourceId(String souceid) {
if (null == souceid) {
mFileTransferRecorder.saveUploadInfo(mResponseId, mFilePath ,"" + mUploadFileSize);
}
}
/**
* 读取buffer数据,并返回读取的大小
*
* @param buffer
* :读取数据的缓冲区
*/
private int readFileData(byte[] buffer) {
int len = READ_FILE_END;
RandomAccessFile accessFile = null;
try {
accessFile = new RandomAccessFile(mUploadFile, "r");
accessFile.seek(mStartedPosition);
len = accessFile.read(buffer);
if (mStartedPosition != mAlreadyUploadLength) {
mAlreadyUploadLength = mStartedPosition;
}
accessFile.close();
} catch (FileNotFoundException e) {
len = READ_FILE_END;
onError(FILE_NOT_FOUND_ERR);
} catch (IOException e) {
len = READ_FILE_END;
onError(FILE_NOT_FOUND_ERR);
}
return len;
}
/**
* 将指定buff的数据写入到目标输出流
*
* @param outStream
* :输入流
* @param buffer
* :要写入的数据
* @param len
* :要写入数据的长度
* @return 实际写入数据大小
*/
private int writeBufferData(OutputStream outStream, byte[] buffer, int len)
throws IOException {
outStream.write(buffer, 0, len);
outStream.flush();
outStream.close();
return len;
}
/**
* 获取从服务端传来的结果码
*
* @param httpConnection
* :http连接
* @return 1:成功接收到文件分块。 0:成功接收到整个文件。 -1:获取结果码出现错误。
*/
private int getResultCode(HttpURLConnection httpConnection) {
DataInputStream dataInputStream = null;
try {
if (HttpURLConnection.HTTP_OK != httpConnection.getResponseCode()) {
return RESULT_CODE_ERROR;
}
// 服务端如果接收成功,会返回RETURN_CODE:1
dataInputStream = new DataInputStream(
httpConnection.getInputStream());
// 处理服务器传过来的response信息
String data = dataInputStream.readLine();
int resultCode = Integer
.valueOf(data.substring(data.indexOf(":") + 1));
return (resultCode == RESULT_CODE_CHUNK_RECEIVED
|| resultCode == RESULT_CODE_FILE_RECEIVED) ? resultCode
: RESULT_CODE_ERROR;
} catch (Exception e) {
e.printStackTrace();
return RESULT_CODE_ERROR;
} finally {
try {
if (null != dataInputStream) {
dataInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 修改进度
*
* @param len
* :本次上传的文件大小
*/
private void doProcessUpdate(int resultCode,int len) {
if(RESULT_CODE_ERROR != resultCode){
mAlreadyUploadLength += len;
XLog.d("xface", "" + mAlreadyUploadLength);
onProgressUpdated(mAlreadyUploadLength, mUploadFileSize);
}
}
/** 获取要上传文件的大小 */
private long getUploadFileSize() {
return mUploadFile.length();
}
/** 获取要上传文件的名字 */
private String getUploadFileName() {
return mUploadFile.getName();
}
/**
* 把文件分成几份上传有2个原因:<br/>
* 1.一次性向流写入大量数据会导致程序崩溃。<br/>
* 2.分成几份上传会使进度条更新更平滑。<br/>
* 获取每次要上传文件块的大小 。<br/>
* 如果文件大小不超过1k,则分成2份上传。<br/>
* 如果文件大小在1k-1M之间,则分成10份上传。<br/>
* 如果文件大小在1M-10M之间,则分成20份上传。<br/>
* 如果文件大小超过10M,则每次上传2M。<br/>
* */
private int getSingleUploadLength() {
// 文件总大小
int totalLength = (int) mUploadFileSize;
//如果文件小于100字节则直接一次上传
if (totalLength < SIZE_KB / DIVIDE_SIZE_TEN) {
return SIZE_KB / DIVIDE_SIZE_TEN;
} else if (totalLength < SIZE_KB) {
return totalLength / DIVIDE_SIZE_TWO;
} else if (totalLength < SIZE_KB * SIZE_KB) {
return totalLength / DIVIDE_SIZE_TEN;
} else if (totalLength < DIVIDE_SIZE_TEN * SIZE_KB * SIZE_KB) {
return totalLength / DIVIDE_SIZE_TWENTY;
} else {
return DIVIDE_SIZE_TWO * SIZE_KB * SIZE_KB;
}
}
/**
* 获取指定url的http连接
*
* @param url
* :url地址
* @return url对应的http连接
* */
private HttpURLConnection getHttpConnection(String url) throws IOException,
MalformedURLException, ProtocolException {
HttpURLConnection httpConnection;
httpConnection = ((HttpURLConnection) new URL(url).openConnection());
httpConnection.setConnectTimeout(mTimeout);
httpConnection.setRequestMethod("POST");
httpConnection.setDoInput(true);
httpConnection.setDoOutput(true);
return httpConnection;
}
@Override
public void transfer(XCallbackContext callbackCtx) {
if (mState == UPLOADING) {
return;
}
mCallbackCtx = callbackCtx;
setState(UPLOADING);
new Thread(new Runnable() {
@Override
public void run() {
upload();
}
}).start();
}
private synchronized void setState(int state) {
mState = state;
}
/**
* 获取指定文件地址对应的文件对象
*/
private File getFile() {
XPathResolver pathResolver = new XPathResolver(mFilePath,
mWebContext.getWorkSpace());
String absoluteFilePath = pathResolver.resolve();
File uploadFile = new File(absoluteFilePath);
if (null != absoluteFilePath) {
uploadFile = new File(absoluteFilePath);
if (uploadFile.exists()) {
return uploadFile;
}
}
return null;
}
@Override
public void pause() {
setState(PAUSE);
}
}