/* 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.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import com.polyvi.xface.app.XApplication; import com.polyvi.xface.extension.XCallbackContext; import com.polyvi.xface.extension.XExtensionContext; import com.polyvi.xface.extension.XExtensionResult; import com.polyvi.xface.util.XFileUtils; import com.polyvi.xface.util.XLog; public class XFileDownloader implements XIFileTransferListener, XIFileTransfer { private static final String CLASS_NAME = XFileDownloader.class.getSimpleName(); private static final int TIME_OUT_MILLISECOND = 5000; /** 定义三种下载的状态:初始化状态,正在下载状态,暂停状态 */ private static final int INIT = 1; private static final int DOWNLOADING = 2; private static final int PAUSE = 3; private int mState = INIT; private static final int CONNECTION_ERR = 3; /**定义下载文件的划分倍数和单位文件大小*/ 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 FILE_NOT_EXIST = 0; /**标示temp文件后缀*/ private static final String TEMP_FILE_SUFFIX = ".temp"; /**网络断了的时候进行重新连接次数*/ private static final int RETRY = 3; /**定义下载重连时间为1秒*/ private static final int RETRY_INTERVAL = 1000; /**定义下载缓冲区大小*/ private int mBufferSize; /** 下载器网络标识 */ private String mUrl; /** 存储下载文件的本地路径 */ private String mLocalFilePath; /** native端js回调的上下文环境 */ private XCallbackContext mCallbackCtx; /** 当前的应用 */ private XApplication mApp; /** 已下载的具体信息 */ private XFileDownloadInfo mDownloadInfo; /** 操作配置文件的对象 */ private XFileTransferRecorder mFileTransferRecorder; /** 下载管理器 */ private XFileTransferManager mFileTransferManager; private Context mContext; private CookieSyncManager mCookieSyncManager; public XFileDownloader(Context context,String url, String localFilePath, XExtensionContext extensionContext, XApplication app, XFileTransferRecorder recorder, XFileTransferManager manager) { init(context,url, localFilePath, extensionContext, app, recorder, manager); } /** 初始化方法 */ private void init(Context context,String url, String localFilePath,XExtensionContext extensionContext, XApplication app, XFileTransferRecorder recorder, XFileTransferManager manager) { mUrl = url; mLocalFilePath = localFilePath; mApp = app; mFileTransferManager = manager; mFileTransferRecorder = recorder; mContext = context; mCookieSyncManager = CookieSyncManager.createInstance(mContext); } /** * 初始化下载信息(如果是第一次下载,执行创建本地文件,获取文件的总大小以及在配置文件中添加该条记录, * 如果不是第一次下载,则从配置文件中取出已经下载了的信息,完成断点续传) */ private void initDownloadInfo() { int totalSize = 0; if (isFirst(mUrl)) { HttpURLConnection connection= null; try { URL url = new URL(mUrl); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(TIME_OUT_MILLISECOND); connection.setRequestMethod("GET"); System.getProperties().setProperty("http.nonProxyHosts",url.getHost()); //设置cookie数据 setCookieProperty(connection, mUrl); if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) { totalSize = connection.getContentLength(); if (-1 != totalSize) { mDownloadInfo = new XFileDownloadInfo(totalSize, 0, mUrl); // 保存mDownloadInfo中的数据到配置文件 mFileTransferRecorder.saveDownloadInfo(mDownloadInfo); } else { XLog.e(CLASS_NAME, "cannot get totalSize"); } // 如果第一次下的时候存在temp文件则删除之 File file = new File(mLocalFilePath + TEMP_FILE_SUFFIX); if (file.exists()) { file.delete(); } } } catch (IOException e) { XLog.e(CLASS_NAME, e.getMessage()); } finally{ if( null != connection) { connection.disconnect(); } } } else { // 得到配置文件中已有的url的下载器的具体信息 mDownloadInfo = mFileTransferRecorder.getDownloadInfo(mUrl); totalSize = mDownloadInfo.getTotalSize(); mDownloadInfo.setCompleteSize(getCompleteSize(mLocalFilePath + TEMP_FILE_SUFFIX)); } mBufferSize = getSingleTransferLength(totalSize); } /** * 判断是否是第一次 下载 */ private boolean isFirst(String url) { return !mFileTransferRecorder.hasDownloadInfo(url); } @Override public void transfer(XCallbackContext callbackCtx) { initDownloadInfo(); if (mState == DOWNLOADING) { return; } mCallbackCtx = callbackCtx; if (null == mDownloadInfo) { onError(CONNECTION_ERR); } else { setState(DOWNLOADING); new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; RandomAccessFile randomAccessFile = null; InputStream is = null; int retry = RETRY; //TODO:以后重连次数可能从配置文件中读取 do { int completeSize = mDownloadInfo.getCompleteSize(); try { URL url = new URL(mUrl); connection = (HttpURLConnection) url .openConnection(); connection.setConnectTimeout(TIME_OUT_MILLISECOND); connection.setRequestMethod("GET"); // 设置范围,格式为Range:bytes x-; connection.setRequestProperty("Range", "bytes=" + completeSize + "-"); //设置cookie setCookieProperty(connection,mUrl); // 文件未下载成功先加个.temp标示 randomAccessFile = new RandomAccessFile( mLocalFilePath + TEMP_FILE_SUFFIX, "rwd"); randomAccessFile.seek(completeSize); // 将要下载的文件写到保存在保存路径下的文件中 is = connection.getInputStream(); byte[] buffer = new byte[mBufferSize]; int length = -1; while ((length = is.read(buffer)) != -1) { try { randomAccessFile.write(buffer, 0, length); } catch (Exception e) { retry = -1; break; } completeSize += length; onProgressUpdated(completeSize, 0); if (PAUSE == mState) { break; } } if (mDownloadInfo.isDownloadCompleted()) { // 文件下载成功后去掉.temp标示 renameFile(mLocalFilePath + TEMP_FILE_SUFFIX, mLocalFilePath); onSuccess(); break; } } catch (IOException e) { if (retry <= 0) { onError(CONNECTION_ERR); XLog.e(CLASS_NAME, e.getMessage()); } // 网络异常,睡1秒超时再连接 try { Thread.sleep(RETRY_INTERVAL); } catch (InterruptedException ex) { XLog.e(CLASS_NAME,"sleep be interrupted",ex); } } finally { try { if (null != is) { is.close(); } if (null != randomAccessFile) { // new URL可能报异常,这种情况下randomAccessFile为null randomAccessFile.close(); } if (null != connection) { // new URL可能报异常,这种情况下connection为null connection.disconnect(); } } catch (IOException e) { XLog.e(CLASS_NAME, e.getMessage()); } } }while ((DOWNLOADING == mState) && (0 < retry--)); } }).start(); } } private synchronized void setState(int state) { mState = state; } @Override public void pause() { setState(PAUSE); } @Override public void onSuccess() { mFileTransferRecorder.deleteDownloadInfo(mUrl); mFileTransferManager.removeFileTranferTask(mApp.getAppId(), mUrl); setState(INIT); JSONObject jsonObj = new JSONObject(); try { jsonObj = XFileUtils.getEntry(mApp.getWorkSpace(), new File(mLocalFilePath)); } catch (JSONException e) { XLog.e(CLASS_NAME, e.getMessage()); } mCallbackCtx.success(jsonObj); } @Override public void onError(int errorCode) { setState(INIT); String fullPath = null; String workspace = mApp.getWorkSpace(); if(mLocalFilePath.equals(workspace)) { fullPath = File.separator; } else { fullPath = mLocalFilePath.substring(workspace.length()); } JSONObject error = new JSONObject(); try { error.put("code", errorCode); error.put("source", mUrl); error.put("target", fullPath); } catch (JSONException e) { XLog.e(CLASS_NAME, e.getMessage()); } mCallbackCtx.error(error); } @Override public void onProgressUpdated(int completeSize, long totalSize) { mDownloadInfo.setCompleteSize(completeSize); JSONObject jsonObj = new JSONObject(); try { jsonObj.put("loaded", completeSize); jsonObj.put("total", mDownloadInfo.getTotalSize()); } catch (JSONException e) { XLog.e(CLASS_NAME, e.getMessage()); } XExtensionResult result = new XExtensionResult(XExtensionResult.Status.PROGRESS_CHANGING, jsonObj); result.setKeepCallback(true); mCallbackCtx.sendExtensionResult(result); } /** * 把下载分成几次更新会使进度条更新更平滑。<br/> * 获取每次要上传文件块的大小 。<br/> * 如果文件大小不超过1k,则分成2次更新。<br/> * 如果文件大小在1k-1M之间,则分成10次更新。<br/> * 如果文件大小在1M-10M之间,则分成20次更新。<br/> * 如果文件大小超过10M,则每次下载2M。<br/> * */ private int getSingleTransferLength(int totalSize) { // 文件总大小 int totalLength = totalSize; //如果文件小于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; } } /** * 文件重命名 * * @param oldName:文件旧名字 * @param newName:文件新名字 */ private void renameFile(String oldName,String newName) { File file = new File(oldName); file.renameTo(new File(newName)); } /** * 获取指定文件大小,如果文件不存在则返回-1 * * @param fileName:文件名字 */ private int getCompleteSize(String fileName) { File file = new File(fileName); if(file.exists()){ return (int)file.length(); } return FILE_NOT_EXIST; } /** * 设置connection的Cookie * @param connection Http连接 * @param domain cookie对应的域 */ private void setCookieProperty(HttpURLConnection connection, String domain) { //Add cookie support mCookieSyncManager.startSync(); String cookie = CookieManager.getInstance().getCookie(domain); if(cookie != null) { connection.setRequestProperty("cookie", cookie); } } }