public class ClientConfig {

    public static final int DEFAULT_RETRIES = 1;
    public static final int DEFAULT_CONCURRENT_REQUEST = 5;
    public static final int DEFAULT_SOCKET_TIMEOUT = 30 * 1000;
    public static final int DEFAULT_CONNECTION_TIMEOUT = 15 * 1000;

    /**
     * 分片上传并发数5~10
     */
    private int maxConcurrentRequest = DEFAULT_CONCURRENT_REQUEST;

    /**
     * response超时的时间，单位为ms
     */
    private int socketTimeout = DEFAULT_SOCKET_TIMEOUT;

    /**
     * 连接超时的时间，单位为ms
     */
    private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;

    /**
     * 重试的次数
     */
    private int maxErrorRetry = DEFAULT_RETRIES;

    private String proxyHost;
    private int proxyPort;

    /**
     *
     * @param maxConcurrentRequest 分片上传并发数5~10
     */
    public ClientConfig(int maxConcurrentRequest) {
        this.maxConcurrentRequest = maxConcurrentRequest;
    }


    /**
     *
     * @param socketTimeout response超时的时间，单位为ms
     * @param connectionTimeout 连接超时的时间，单位为ms
     */
    public ClientConfig(int socketTimeout, int connectionTimeout) {
        this.socketTimeout = socketTimeout;
        this.connectionTimeout = connectionTimeout;
    }

    /**
     *
     * @param maxConcurrentRequest 分片上传并发数5~10
     * @param socketTimeout response超时的时间，单位为ms
     * @param connectionTimeout 连接超时的时间，单位为ms
     * @param maxErrorRetry 重试的次数
     */
    public ClientConfig(int maxConcurrentRequest, int socketTimeout, int connectionTimeout, int maxErrorRetry) {
        this.maxConcurrentRequest = maxConcurrentRequest;
        this.socketTimeout = socketTimeout;
        this.connectionTimeout = connectionTimeout;
        this.maxErrorRetry = maxErrorRetry;
    }

    /**
     * 构造新实例。
     */
    public ClientConfig() {
    }

    /**
     * 获取一个默认实例
     */
    public static ClientConfig getDefaultConf() {
        return new ClientConfig();
    }

    /**
     * 返回最大的并发HTTP请求数
     *
     * @return
     */
    public int getMaxConcurrentRequest() {
        return maxConcurrentRequest;
    }

    /**
     * 设置允许并发的最大HTTP请求数
     *
     * @param maxConcurrentRequest 最大HTTP并发请求数
     */
    public void setMaxConcurrentRequest(int maxConcurrentRequest) {
        this.maxConcurrentRequest = maxConcurrentRequest;
    }

    /**
     * 返回通过打开的连接传输数据的超时时间（单位：毫秒）。
     * 0表示无限等待（但不推荐使用）。
     *
     * @return 通过打开的连接传输数据的超时时间（单位：毫秒）。
     */
    public int getSocketTimeout() {
        return socketTimeout;
    }

    /**
     * 设置通过打开的连接传输数据的超时时间（单位：毫秒）。
     * 0表示无限等待（但不推荐使用）。
     *
     * @param socketTimeout 通过打开的连接传输数据的超时时间（单位：毫秒）。
     */
    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    /**
     * 返回建立连接的超时时间（单位：毫秒）。
     *
     * @return 建立连接的超时时间（单位：毫秒）。
     */
    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    /**
     * 设置建立连接的超时时间（单位：毫秒）。
     *
     * @param connectionTimeout 建立连接的超时时间（单位：毫秒）。
     */
    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    /**
     * 返回一个值表示当可重试的请求失败后最大的重试次数。（默认值为2）
     *
     * @return 当可重试的请求失败后最大的重试次数。
     */
    public int getMaxErrorRetry() {
        return maxErrorRetry;
    }

    /**
     * 设置一个值表示当可重试的请求失败后最大的重试次数。（默认值为2）
     *
     * @param maxErrorRetry 当可重试的请求失败后最大的重试次数。
     */
    public void setMaxErrorRetry(int maxErrorRetry) {
        this.maxErrorRetry = maxErrorRetry;
    }

//    public String getProxyHost() {
//        return proxyHost;
//    }
//
//    public void setProxyHost(String proxyHost) {
//        this.proxyHost = proxyHost;
//    }
//
//    public int getProxyPort() {
//        return proxyPort;
//    }
//
//    public void setProxyPort(int proxyPort) {
//        this.proxyPort = proxyPort;
//    }
} public class Config {

    public static final String PUT_URL = "your upload domain";
    public static final String GET_URL = "your bucket domain";
    public static final String MGR_URL = "your manage domain";


    public static String VERSION = "1.6.8";
    public static boolean DEBUGGING = false;

    //不设置默认url，必须由用户填充
    public static String baseUrl = PUT_URL;

    private Config() {
    }

} public class LogRecorder {

    private static final String FILE_NAME = "wcs-dump.log";
    private String mFilePathString = "";
    private volatile Boolean mLock = false;

    private LogRecorder() {
    }

    public static LogRecorder getInstance() {
        return SingletonHolder.sInstance;
    }

    public void setup(Context context) {
        mFilePathString = context.getFilesDir() + File.separator + FILE_NAME;
    }

    public void enableLog() {
        mLock = true;
    }

    public void disableLog() {
        mLock = false;
    }

    public Boolean getLock() {
        return mLock;
    }

    public synchronized void dumpLog(String str) {
        synchronized (mLock) {
            if (!mLock) {
                return;
            }
            if (!TextUtils.isEmpty(mFilePathString)) {
                File logFile = new File(mFilePathString);
                if (!logFile.exists()) {
                    try {
                        logFile.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
                    buf.append(str);
                    buf.newLine();
                    buf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                Log.e("CNCLog", "Log recorder hava not setted up.");
            }
        }
    }

    private static class SingletonHolder {
        private static LogRecorder sInstance = new LogRecorder();
    }

} public class OperationMessage {
    private int status;
    private String message;
    private Throwable error;

    public static OperationMessage fromJsonString(String jsonString, String requestId) throws JSONException {
        OperationMessage errorMessage = new OperationMessage();
        if(!TextUtils.isEmpty(jsonString)) {
            JSONObject jsonObject = new JSONObject(jsonString);
            jsonObject.put("X-Reqid", requestId);
            errorMessage.message = jsonObject.toString();
        }

        return errorMessage;
    }

    public OperationMessage() {
    }

    public OperationMessage(int status, String message) {
        this.status = status;
        this.message = message;
        this.error = null;
    }

    public OperationMessage(Throwable error) {
        this.error = error;
    }

    public int getStatus() {
        return this.status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        StringBuilder formatMessage = new StringBuilder();
        if(this.message != null) {
            formatMessage.append(this.message);
        }

        if(this.error != null) {
            formatMessage.append(" { ");
            formatMessage.append("ClientMsg: ");
            formatMessage.append(WCSLogUtil.getStackTraceString(this.error));
            formatMessage.append(" }");
        }

        return formatMessage.toString();
    }

    public void setMessage(String message) {
        this.message = message;
    }
} public class SliceCache {

    public String uploadBatch;
    private String fileHash;//key
    /**
     * {
     * "ctx":          "<Ctx           string>",
     * "checksum":     "<Checksum      string>",
     * "crc32":         "<Crc32         int64>",
     * "offset":        "<Offset        int64>"
     * }
     */
    private ArrayList<String> blockContext;
    private ArrayList<Integer> blockUploadedIndex;

    private long sliceSize;
    private long blockSize;

    public void setBlockSize(long blockSize) {
        this.blockSize = blockSize;
    }

    public long getBlockSize() {
        return this.blockSize;
    }

    public void setSliceSize(long sliceSize) {
        this.sliceSize = sliceSize;
    }

    public long getSliceSize() {
        return this.sliceSize;
    }


    public String getUploadBatch() {
        if (null == uploadBatch) {
            return "";
        }
        return uploadBatch;
    }

    public void setUploadBatch(String uploadBatch) {
        this.uploadBatch = uploadBatch;
    }

    public String getFileHash() {
        if (null == fileHash) {
            return "";
        }
        return fileHash;
    }

    public void setFileHash(String fileHash) {
        this.fileHash = fileHash;
    }

    public ArrayList<String> getBlockContext() {
        if (null == blockContext) {
            return new ArrayList<String>();
        }
        return blockContext;
    }

    public void setBlockContext(ArrayList<String> blockContext) {
        this.blockContext = blockContext;
    }

    public ArrayList<Integer> getBlockUploadedIndex() {
        if (null == blockUploadedIndex) {
            return new ArrayList<Integer>();
        }
        return blockUploadedIndex;
    }

    public void setBlockUploadedIndex(ArrayList<Integer> blockUploadedIndex) {
        this.blockUploadedIndex = blockUploadedIndex;
    }

    @Override
    public String toString() {
        String cacheString = fileHash;
        cacheString += "; context ";
        for (String context : getBlockContext()) {
            cacheString += ("\t" + context);
        }
        cacheString += ";";
        cacheString += "; index ";
        for (Integer index : getBlockUploadedIndex()) {
            cacheString += ("\t" + index);
        }
        cacheString += ";";
        return cacheString;
    }

} public class SliceCacheManager {

    private static SliceCacheManager sInstance;
    private static HashSet<SliceCache> sCacheSet;

    private SliceCacheManager() {
        sCacheSet = new HashSet<SliceCache>();
    }

    public static synchronized SliceCacheManager getInstance() {
        if (null == sInstance) {
            sInstance = new SliceCacheManager();
        }
        return sInstance;
    }

    public synchronized void addSliceCache(SliceCache sliceCache) {
        if (null == sliceCache) {
            return;
        }
        if (null == sliceCache.getFileHash() || sliceCache.getFileHash().length() <= 0) {
            return;
        }
        if (sliceCache.getBlockContext().size() <= 0) {
            return;
        }
        if (sliceCache.getBlockUploadedIndex().size() <= 0) {
            return;
        }

        if (sliceCache.getBlockContext().size() != sliceCache.getBlockUploadedIndex().size()) {
            return;
        }
        for (SliceCache cache : sCacheSet) {
            if (cache.getFileHash().equals(sliceCache.getFileHash())) {
                //已存在则替换
                sCacheSet.remove(cache);
            }
        }
        sCacheSet.add(sliceCache);
    }

    public synchronized SliceCache getSliceCache(String fileHash) {
        if (null != fileHash && fileHash.length() >= 0) {
            for (SliceCache sliceCache : sCacheSet) {
                if (sliceCache.getFileHash().equals(fileHash)) {
                    return sliceCache;
                }
            }
        }
        return null;
    }

    public synchronized void removeSliceCache(SliceCache sliceCache) {
        if (null != sliceCache) {
            sCacheSet.remove(sliceCache);
        }
    }

    public synchronized void dumpAll() {
        for (SliceCache cache : sCacheSet) {
            WCSLogUtil.i("cache : " + cache);
        }
    }

} public class SliceResponse extends WcsResult {

    /**
     * 下一个上传片在切割块中的偏移。若片大小与块大小相等，则该返回值为该块的大小。
     */
    public long offset;

    /**
     * 本次上传成功后的块级上传控制信息，用于后续上传片及生成文件。本字段是只能被WCS服务器解读使用的不透明字段，上传端不应修改其内容。
     * 每次返回的<ctx>都只对应紧随其后的下一个上传数据片，上传非对应数据片会返回401状态码。
     */
    public String context;

    /**
     * 上传块Crc32，客户可通过此字段对上传块的完整性进行较验。
     */
    public long crc32;

    /**
     * 上传块校验码。
     */
    public String md5;

    public static void fromJsonString(SliceResponse sliceResponse, String jsonString) throws JSONException {
        if (!TextUtils.isEmpty(jsonString)) {
            JSONObject jsonObject = new JSONObject(jsonString);
            sliceResponse.offset = jsonObject.optLong("offset", 0);
            sliceResponse.context = jsonObject.optString("ctx", "0");
            sliceResponse.crc32 = jsonObject.optLong("crc32", 0);
            sliceResponse.md5 = jsonObject.optString("checksum", "0");
            if (sliceResponse.crc32 == 0 || "0".equals(sliceResponse.context)) {
                throw new JSONException("crc32 or context not found: " + jsonString);
            }
        }
    }

    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.putOpt("offset", offset);
            jsonObject.putOpt("context", context);
            jsonObject.putOpt("crc32", crc32);
            jsonObject.putOpt("md5", md5);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return jsonObject.toString();
    }

} public class SliceUploadRequest extends WcsRequest {

    //分片上传进度回调（每片回调一次）
    private WcsProgressCallback<WcsRequest> progressCallback;

    public WcsProgressCallback<WcsRequest> getProgressCallback() {
        return progressCallback;
    }

    public void setProgressCallback(WcsProgressCallback<WcsRequest> progressCallback) {
        this.progressCallback = progressCallback;
    }
} public class UploadFileRequest extends WcsRequest {
    //token中已包含bucket
//    private String bucketName;
    private String objectKey;

    private Map<String, String> callbackParam;

    private Map<String, String> callbackVars;

    //普通上传进度回调（1/10文件回调一次，可在WcsRequestTask修改）
    private WcsProgressCallback<UploadFileRequest> progressCallback;

    public String getObjectKey() {
        return objectKey;
    }

    public void setObjectKey(String objectKey) {
        this.objectKey = objectKey;
    }

    public Map<String, String> getCallbackParam() {
        return callbackParam;
    }

    public void setCallbackParam(Map<String, String> callbackParam) {
        this.callbackParam = callbackParam;
    }

    public Map<String, String> getCallbackVars() {
        return callbackVars;
    }

    public void setCallbackVars(Map<String, String> callbackVars) {
        this.callbackVars = callbackVars;
    }

    public WcsProgressCallback<UploadFileRequest> getProgressCallback() {
        return progressCallback;
    }

    public void setProgressCallback(WcsProgressCallback<UploadFileRequest> progressCallback) {
        this.progressCallback = progressCallback;
    }
} public interface WcsCompletedCallback<T1, T2 extends WcsResult> {
    public void onSuccess(T1 request, T2 result);

    public void onFailure(T1 request, OperationMessage operationMessage);
//    public void onFailure(T1 request, Exception e);
} public interface WcsProgressCallback<T> {
    public void onProgress(T request, long currentSize, long totalSize);
} public abstract class FileUploaderListener extends FileUploaderStringListener {

    public final void onSuccess(int status, String responseString) {
        try {
            onSuccess(status, BaseApi.parseWCSUploadResponse(result));
        } catch (JSONException e) {
            onFailure(new OperationMessage(0, e.getMessage()));
        }
    }

    /**
     * 文件上传成功之后回调
     *
     * @param status
     * @param responseJson
     */
    public abstract void onSuccess(int status, JSONObject responseJson);

} public abstract class FileUploaderStringListener implements
        WcsCompletedCallback<UploadFileRequest, UploadFileResult>,
        WcsProgressCallback<UploadFileRequest> {
    UploadFileResult result;

    @Override
    public void onProgress(UploadFileRequest request, long currentSize, long totalSize) {


    }

    @Override
    public void onSuccess(UploadFileRequest request, UploadFileResult result) {
        this.result = result;
        onSuccess(result.getStatusCode(), result.getResponse());
    }

    @Override
    public void onFailure(UploadFileRequest request, OperationMessage message) {

        onFailure(message);
    }

    /**
     * 文件上传成功之后回调
     *
     * @param status
     * @param responseString
     */
    public abstract void onSuccess(int status, String responseString);

    /**
     * 文件上传失败之后的回调
     *
     * @param operationMessage 操作对应的信息
     */
    public abstract void onFailure(OperationMessage operationMessage);


} public abstract class SliceUploaderListener {

    public abstract void onSliceUploadSucceed(JSONObject reponseJSON);

    public abstract void onSliceUploadFailured(HashSet<String> errorMessages);

    public void onProgress(long uploaded, long total) {

    }

} public enum HttpMethod {
    /**
     * DELETE方法。
     */
    DELETE,

    /**
     * GET方法。
     */
    GET,

    /**
     * HEAD方法。
     */
    HEAD,

    /**
     * POST方法。
     */
    POST,

    /**
     * PUT方法。
     */
    PUT,

    /**
     * OPTION方法。
     */
    OPTIONS
} public class WcsRequest {

    //base url
    private String url;
    private HttpMethod method;
    //请求头
    private Map<String, String> headers = new HashMap<>();
    //请求参数
    private Map<String, String> parameters = new LinkedHashMap<>();

    private byte[] uploadData;
    //上传文件的路径
    private String uploadFilePath;
    private InputStream uploadInputStream;

    private File file;
    //    private String key;

    //文件名

    private String name;

    private long readStreamLength;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }

    public long getReadStreamLength() {
        return readStreamLength;
    }

    public void setReadStreamLength(long readStreamLength) {
        this.readStreamLength = readStreamLength;
    }

    public byte[] getUploadData() {
        return uploadData;
    }

    public void setUploadData(byte[] uploadData) {
        this.uploadData = uploadData;
    }

    public String getUploadFilePath() {
        return uploadFilePath;
    }

    public void setUploadFilePath(String uploadFilePath) {
        this.uploadFilePath = uploadFilePath;
    }

    public InputStream getUploadInputStream() {
        return uploadInputStream;
    }

    public void setUploadInputStream(InputStream uploadInputStream) {
        this.uploadInputStream = uploadInputStream;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public HttpMethod getMethod() {
        return method;
    }

    public void setMethod(HttpMethod method) {
        this.method = method;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }

    public Map<String, String> getParameters() {
        return parameters;
    }

    public void setParameters(Map<String, String> parameters) {
        this.parameters = parameters;
    }


    public void releaseData() {
        if (uploadData != null && uploadData.length > 0) {
            uploadData = null;
        }
    }
} public class WcsResult {

    public static final String REQUEST_ID = "X-Reqid";
    private int statusCode;

    private Map<String, String> responseHeader;

    private String requestId;

    private String response;

    /**
     * 响应结果的HTTP响应码
     *
     * @return HTTP响应码
     */
    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    /**
     * 响应结果的HTTP响应头部
     *
     * @return 所有HTTP响应头
     */
    public Map<String, String> getResponseHeader() {
        return responseHeader;
    }

    public void setResponseHeader(Map<String, String> responseHeader) {
        this.responseHeader = responseHeader;
    }

    /**
     * 成功请求的RequestId
     *
     * @return 标识唯一请求的RequestId
     */
    public String getRequestId() {
        return requestId;
    }

    public void setRequestId(String requestId) {
        this.requestId = requestId;
    }

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
    }

    public String getHeaders() {
        StringBuilder headersStringBuffer = new StringBuilder();
        for (Object key : responseHeader.keySet()) {
            headersStringBuffer.append(key)
                    .append(" : ")
                    .append(responseHeader.get(key))
                    .append(" , ");
        }
        return headersStringBuffer.toString();
    }

} public class Block {

    private static final int PER_SLICE_SIZE = 64 * 1024;
    private static final int MAX_BLOCK_SIZE = 100 * 1024 * 1024;
    private static final int DEFAULT_BLOCK_SIZE = 4 * 1024 * 1024;
    private static final int DEFAULT_SLICE_SIZE = 4 * 1024 * 1024;

    private static int sDefaultSliceSize = DEFAULT_SLICE_SIZE;
    private static long sDefaultBlockSize = DEFAULT_BLOCK_SIZE;

    private RandomAccessFile mRandomAccessFile;
    private long mStart;
    private long mSize;//blocksize 块实际大小，最后一块与其他块大小可能不同
    private int mSliceSize;
    private int mSliceIndex;
    private long mOriginalFileSize;
    private String mFileName;
    private int blockIndex;

    public ByteArray getByteArray() {
        return mByteArray;
    }

    public void setByteArray(ByteArray byteArray) {
        mByteArray = byteArray;
    }

    private ByteArray mByteArray;

    Block(RandomAccessFile randomAccessFile, String fileName, long start, long blockSize, int sliceSize,int blockIndex) throws IOException {
        mRandomAccessFile = randomAccessFile;
        mOriginalFileSize = randomAccessFile.length();
        mFileName = fileName;
        mStart = start;
        mSize = blockSize;
        mSliceSize = sliceSize;
        this.blockIndex = blockIndex;
    }


    public static Block[] blocks(File file) {
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException e) {
            Log.e("CNCLog", "file not found : " + file);
            return null;
        }

        long fileSize = 0;
        try {
            fileSize = randomAccessFile.length();
        } catch (IOException e) {
            WCSLogUtil.e(e.getMessage());
        }
        if (fileSize == 0) {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
            }
            return null;
        }

        int blockCount = (int) ((fileSize + sDefaultBlockSize - 1) / sDefaultBlockSize);// TODO: 2017/5/3
        WCSLogUtil.d(String.format(Locale.CHINA, "file size : %s, block count : %s", fileSize, blockCount));
        Block[] blocks = new Block[blockCount];
        long blockSize = sDefaultBlockSize;
        for (int i = 0; i < blockCount; i++) {
            if (i + 1 == blockCount) {
                long remain = fileSize % sDefaultBlockSize;
                blockSize = remain == 0 ? sDefaultBlockSize : remain;// TODO: 2017/5/2 最后一块
            }
            try {
                blocks[i] = new Block(new RandomAccessFile(file, "r"), file.getName(), i * sDefaultBlockSize, blockSize, sDefaultSliceSize,i);
//                blocks[i] = new Block(randomAccessFile, file.getName(), i * sDefaultBlockSize, blockSize, sDefaultSliceSize,i);
                //有多少块就创建多少空间
                // TODO: 2017/5/4 需要优化,等真正执行块上传时再创建
//				blocks[i].mByteArrayBuffer = new ByteArrayBuffer(sDefaultSliceSize);// TODO: 2017/5/2 初始化slice
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return blocks;
    }

    public void clear() {
        try {
            mRandomAccessFile.close();
        } catch (IOException e) {
        }
    }

    public void releaseBuffer() {
        if (mByteArray != null) {
            mByteArray.clear();
        }
    }

    public Slice moveToNext() {
        return getSlice(mSliceIndex++);
    }

    public void setIndex(int index) {
        mSliceIndex = index;
    }

    public int getIndex() {
        return mSliceIndex;
    }

    public Slice moveToIndex(int index) {
        mSliceIndex = index;
        return moveToNext();
    }

    public Slice lastSlice() {
        return getSlice(mSliceIndex - 1);
    }

    private Slice getSlice(int index) {
        //如果没有创建buffer，创建之
        if (mByteArray == null) {
            mByteArray = new ByteArray(getSliceSize());
        }
        long offset = mStart + index * getSliceSize();//从1开始
        if (index * getSliceSize() >= mSize) {
            return null;
        }
        int sliceSize = getSliceSize();
        if ((offset + getSliceSize()) > (mStart + mSize)) {//计算最后一片
            sliceSize = (int) (mSize % getSliceSize());
        }
        byte[] sliceData = mByteArray.toBuffer();
        Arrays.fill(sliceData, (byte) 0);
        if (sliceSize < getSliceSize()) {
            sliceData = new byte[sliceSize];
        }

        try {
            mRandomAccessFile.seek(offset);
//            try {
//                Thread.sleep(500L);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            mRandomAccessFile.read(sliceData, 0, sliceSize);
//            WCSLogUtil.d("block index : "+blockIndex+"; offset : " + offset + "; slice size : " + sliceSize+" sliceEtag:"+ WetagUtil.getEtagHash(sliceData));
            WCSLogUtil.d("block index : "+blockIndex+"; offset : " + offset + "; slice size : " + sliceSize);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (sliceSize < getSliceSize()) { // 利用同一个缓存
            return new Slice(index * getSliceSize(), sliceData);//记录最后一片
        } else {
            return new Slice(index * getSliceSize(), mByteArray);
        }
    }

    public long size() {
        return mSize;
    }

    public int getSliceSize() {
        return mSliceSize;
    }

    public String toString() {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("start", mStart);
            jsonObject.put("size", mSize);
            jsonObject.put("slice index", mSliceIndex);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        try {
            return jsonObject.toString(4);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return "Block<>";
    }

    public long getOriginalFileSize() {
        return mOriginalFileSize;
    }

    public String getOriginalFileName() {
        return mFileName;
    }

//    public void initFirstSlice() {
//        mByteArrayOutputStream = new ByteArrayOutputStream(sDefaultSliceSize);
//    }

    /**
     * 设置块的大小，默认为4M
     * 注意：块的大小必须是4M的倍数，最大不能超过100M
     *
     * @param size
     */
    public static void setBlockSize(int size) {
        size = size * 1024 * 1024;
        if (size <= 0 || size % sDefaultBlockSize != 0 || size > MAX_BLOCK_SIZE) {
            sDefaultBlockSize = DEFAULT_BLOCK_SIZE;
            return;
        }
        sDefaultBlockSize = size;
        WCSLogUtil.d("block size: " + sDefaultBlockSize);
    }

    /**
     * 设置片的大小，默认为256KB
     * 注意：片的大小必须是64K的倍数，最大不能超过块的大小。
     *
     * @param size
     */
    public static void setSliceSize(int size) {
        size = size * 1024;
        if (size <= 0 || size % PER_SLICE_SIZE != 0 || size > sDefaultBlockSize) {
            sDefaultSliceSize = DEFAULT_SLICE_SIZE;
            return;
        }
        sDefaultSliceSize = size;
        WCSLogUtil.d("slice size: " + sDefaultSliceSize);
    }

} public class ByteArray {
    private byte[] buffer;

    public ByteArray(int capacity) {

        buffer = new byte[capacity];
    }

    public byte[] toBuffer() {
        return buffer == null ? new byte[0] : buffer;
    }

    public void clear() {
        buffer = null;
    }

    public int size() {
        return buffer == null ? 0 : buffer.length;
    }
} public class Slice {

    public static final int SLICE_MAX_RETRY = 2;
    private byte[] mData;
    private long mOffset;
    private ByteArray mByteArray;

    /**
     * 上传完会校验正确性，如果不正确则需要重试
     * 重试次数
     */
    private int mRetry;

    Slice(long offset, ByteArray byteArray) {
        this.mOffset = offset;
        this.mByteArray = byteArray;
    }

    Slice(long offset, byte[] data) {
        mOffset = offset;
        mData = data;
    }

    public long size() {
        if (mByteArray != null) {
            return mByteArray.size();
        } else if (mData != null) {
            return mData.length;
        }
        return 0;
    }

    public byte[] toByteArray() {
        if (null != mByteArray) {
            return mByteArray.toBuffer();
        } else if (null != mData) {
            return mData;
        }
        return new byte[0];
    }

    public long getOffset() {
        return mOffset;
    }

    public int getRetry() {
        return mRetry;
    }

    public void setRetry(int retry) {
        this.mRetry = retry;
    }
} public class Crc32 {

    /**
     * 4
     */
    public static final int FOUR = 4;
    /**
     * 0xf
     */
    public static final int TEMP = 0xf;

    public static long calc(byte[] data, int offset, int length) {
        CRC32 crc32 = new CRC32();
        crc32.update(data, offset, length);
        return crc32.getValue();
    }

    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);
        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) {
            crc32.update(buffer, 0, read);
        }
        return crc32.getValue();
    }

} public class EncodeUtils {

    public static byte[] urlsafeEncodeBytes(byte[] src) {
        if (src.length % 3 == 0) {
            return encodeBase64Ex(src);
        }

        byte[] b = encodeBase64Ex(src);
        if (b.length % 4 == 0) {
            return b;
        }

        int pad = 4 - b.length % 4;
        byte[] b2 = new byte[b.length + pad];
        System.arraycopy(b, 0, b2, 0, b.length);
        b2[b.length] = '=';
        if (pad > 1) {
            b2[b.length + 1] = '=';
        }
        return b2;
    }

    public static byte[] urlsafeBase64Decode(String encoded) {
        byte[] rawbs = encoded.getBytes();
        for (int i = 0; i < rawbs.length; i++) {
            if (rawbs[i] == '_') {
                rawbs[i] = '/';
            } else if (rawbs[i] == '-') {
                rawbs[i] = '+';
            }
        }
        return Base64.decode(rawbs, Base64.NO_WRAP);
    }

    public static String urlsafeDecodeString(String encoded) {
        return new String(urlsafeBase64Decode(encoded));
    }

    public static String urlsafeEncodeString(byte[] src) {
        return new String(urlsafeEncodeBytes(src));
    }

    public static String urlsafeEncode(String text) {
        try {
            return new String(urlsafeEncodeBytes(text.getBytes("UTF-8")), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    // replace '/' with '_', '+" with '-'
    private static byte[] encodeBase64Ex(byte[] src) {
        // urlsafe version is not supported in version 1.4 or lower.
        byte[] b64 = Base64.encode(src, Base64.NO_WRAP);

        for (int i = 0; i < b64.length; i++) {
            if (b64[i] == '/') {
                b64[i] = '_';
            } else if (b64[i] == '+') {
                b64[i] = '-';
            }
        }
        return b64;
    }

    /**
     * MD5加密
     *
     * @param str
     * @return
     */
    public static String MD5(String str) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        char[] charArray = str.toCharArray();
        byte[] byteArray = new byte[charArray.length];
        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString().toLowerCase();
    }

} public class FileUtil {

    public static File getFile(Context context, Uri uri) {
        uri = fileUri(context, uri);
        try {
            return new File(uri.getPath());
        } catch (Exception e) {
        }
        return null;
    }

    private static Uri fileUri(Context context, Uri uri) {
        Uri fileUri = uri;
        if (!uri.toString().startsWith("file")) {
            String filePath;
            if (uri != null && "content".equals(uri.getScheme())) {
                Cursor cursor = context.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();
            }
            fileUri = Uri.parse("file://" + filePath);
        }
        return fileUri;
    }

    public static void copyFile(String fromFile, String toFile) {
        File file = new File(toFile);
        if (file.exists()) {
            if (!file.delete()) {
                WCSLogUtil.w("failed to delete " + toFile);
            }
        }
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = new FileInputStream(fromFile);
            fos = new FileOutputStream(toFile);
            byte[] b = new byte[1024];
            int count = 0;
            while ((count = is.read(b)) != -1) {
                fos.write(b, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 执行拷贝任务
     *
     * @param asset 需要拷贝的assets文件路径
     * @return 拷贝成功后的目标文件句柄
     * @throws IOException
     */
    public static File copyFromAsset(String asset, Context context,String destinationPath) throws IOException {

        InputStream source = context.getAssets().open(asset);

        File destinationFile = new File(destinationPath, asset);
        destinationFile.getParentFile().mkdirs();
        OutputStream destination = new FileOutputStream(destinationFile);
        byte[] buffer = new byte[1024];
        int nread;

        while ((nread = source.read(buffer)) != -1) {
            if (nread == 0) {
                nread = source.read();
                if (nread < 0)
                    break;
                destination.write(nread);
                continue;
            }
            destination.write(buffer, 0, nread);
        }
        destination.close();

        return destinationFile;
    }

    /**
     * 返回当前sdcard的路径，如果sdcard不存在则返回空。
     *
     * @return
     */
    public static String getSdcardPath() {
        String sdcardRootPath = null;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            sdcardRootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        }
        return sdcardRootPath;
    }

} public class WCSLogUtil {

    private static final String TAG = "CNCLog";

    private static boolean DEBUGGING = Config.DEBUGGING;

    public static boolean isEnableLog() {
        return DEBUGGING;
    }

    public static String getStackTraceString(Throwable tr) {
        if (tr == null) {
            return "Throwable is null";
        }

        if (tr.getLocalizedMessage() != null && !"null".equals(tr.getLocalizedMessage())) {
            return tr.getLocalizedMessage();
        }

        tr.getStackTrace();

        Throwable t = tr;
        while (t != null) {
            t = t.getCause();
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tr.printStackTrace(pw);
        pw.flush();
        String stackTrace = sw.toString();
        try {
            sw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        pw.close();
        return stackTrace;
    }

    public static void v(String msg) {
        WCSLogUtil.v(null, msg);
    }

    public static void v(Class<?> clazz, String msg) {
        if (DEBUGGING) {
            Log.v(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void v(Class<?> clazz, String msg, Throwable tr) {
        if (DEBUGGING) {
            Log.v(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void d(Class<?> clazz, String msg) {
        if (DEBUGGING) {
            Log.d(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void d(Class<?> clazz, String msg, Throwable tr) {
        if (DEBUGGING) {
            Log.d(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void d(String msg) {
        WCSLogUtil.d(null,  msg);
    }

    public static void i(Class<?> clazz, String msg) {
        if (DEBUGGING) {
            Log.i(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void i(Class<?> clazz, String msg, Throwable tr) {
        if (DEBUGGING) {
            Log.i(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void i(String msg) {
        WCSLogUtil.i(null, msg);
    }

    public static void w(Class<?> clazz, String msg) {
        if (DEBUGGING) {
            Log.w(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void w(Class<?> clazz, String msg, Throwable tr) {
        if (DEBUGGING) {
            Log.w(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void w(String msg) {
        WCSLogUtil.w(null, msg);
    }

    public static void e(Class<?> clazz, String msg) {
        if (DEBUGGING) {
            Log.e(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void e(Class<?> clazz, String msg, Throwable tr) {
        if (DEBUGGING) {
            Log.e(null == clazz ? TAG : getTag(clazz), Thread.currentThread().getName() +" "+ msg);
        }
    }

    public static void e(String msg) {
        WCSLogUtil.e(null,  msg);
    }

    private static String getTag(Class<?> clazz) {
        return "[CNCLog" + clazz.getSimpleName() + "]";
    }
} static final String FORM_TOKEN = "token"; static synchronized InternalRequest getInternalRequest(Context context, ClientConfig config) {
    synchronized (mObject) {
        if (null == mInternalRequest) {
            mInternalRequest = new InternalRequest(config == null ? ClientConfig.getDefaultConf() : config);
        }
        if (context != null) {
            LogRecorder.getInstance().setup(context.getApplicationContext());
        }
    }
    return mInternalRequest;
}
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import com.chinanetcenter.wcs.android.ClientConfig;
import com.chinanetcenter.wcs.android.Config;
import com.chinanetcenter.wcs.android.LogRecorder;
import com.chinanetcenter.wcs.android.entity.OperationMessage;
import com.chinanetcenter.wcs.android.entity.SliceCache;
import com.chinanetcenter.wcs.android.entity.SliceCacheManager;
import com.chinanetcenter.wcs.android.entity.SliceResponse;
import com.chinanetcenter.wcs.android.internal.SliceUploadRequest;
import com.chinanetcenter.wcs.android.internal.UploadFileRequest;
import com.chinanetcenter.wcs.android.internal.WcsCompletedCallback;
import com.chinanetcenter.wcs.android.internal.WcsProgressCallback;
import com.chinanetcenter.wcs.android.listener.FileUploaderListener;
import com.chinanetcenter.wcs.android.listener.FileUploaderStringListener;
import com.chinanetcenter.wcs.android.listener.SliceUploaderListener;
import com.chinanetcenter.wcs.android.network.HttpMethod;
import com.chinanetcenter.wcs.android.network.WcsRequest;
import com.chinanetcenter.wcs.android.network.WcsResult;
import com.chinanetcenter.wcs.android.slice.Block;
import com.chinanetcenter.wcs.android.slice.ByteArray;
import com.chinanetcenter.wcs.android.slice.Slice;
import com.chinanetcenter.wcs.android.utils.Crc32;
import com.chinanetcenter.wcs.android.utils.EncodeUtils;
import com.chinanetcenter.wcs.android.utils.FileUtil;
import com.chinanetcenter.wcs.android.utils.WCSLogUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Stack;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import static com.chinanetcenter.wcs.android.Config.baseUrl;
import static com.chinanetcenter.wcs.android.api.BaseApi.FORM_TOKEN;
import static com.chinanetcenter.wcs.android.api.BaseApi.getInternalRequest;
                if (null != sliceUploaderListener) {
                    try {
                        sliceUploaderListener.onSliceUploadSucceed(BaseApi.parseWCSUploadResponse(result));
                    } catch (JSONException e) {
                        onFailure(request, new OperationMessage(e));
                    }
                }
            }

            @Override
            public void onFailure(WcsRequest request, OperationMessage operationMessage) {
                WCSLogUtil.d("merge block failured : " + operationMessage.getMessage());
                HashSet<String> hashSet = new HashSet<String>();
                hashSet.add(String.format(SLICE_UPLOAD_MESSAGE_FORMAT, -1, operationMessage.getMessage()));
                if (null != sliceUploaderListener) {
                    sliceUploaderListener.onSliceUploadFailured(hashSet);
                }
            }


        };

        String mergeUrlString = baseUrl + "/mkfile/" + fileSize;
        StringBuilder mergeUrlStringBuffer = new StringBuilder();
        if (null != customParams && customParams.size() > 0)

        {
            for (String key : customParams.keySet()) {
                String value = customParams.get(key);
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                    mergeUrlStringBuffer.append("/");
                    mergeUrlStringBuffer.append(key);
                    mergeUrlStringBuffer.append("/");
                    mergeUrlStringBuffer.append(EncodeUtils.urlsafeEncode(value));
                }
            }
            mergeUrlString += mergeUrlStringBuffer.toString();
        }


        WcsRequest request = new WcsRequest();
        request.setMethod(HttpMethod.POST);
        request.setUrl(mergeUrlString);
        request.setUploadData(contextList.getBytes());
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", uploadToken);
        headers.put("UploadBatch", sliceCache.getUploadBatch());
        headers.put("Content-Type", "text/plain");
        if (conf != null) {
            if (!TextUtils.isEmpty(conf.mimeType)) {
                headers.put("MimeType", conf.mimeType);
            }
            //key需要URL安全的Base64编码
            if (!TextUtils.isEmpty(conf.keyName))
                headers.put("Key", EncodeUtils.urlsafeEncode(conf.keyName));
            //设置文件过期时间
            if (!TextUtils.isEmpty(conf.deadline))
                headers.put("Deadline", conf.deadline);
        }
        request.setHeaders(headers);

        getInternalRequest(context, sClientConfig).mergeBlock(tag, request, wcsCompletedCallback, context);

        dump(context, uploadToken, mergeUrlString, fileSize, "unknown");
    }

    /**
     * 上传块
     *
     * @param context             上下文
     * @param uploadToken         上传token
     * @param block               块
     * @param blockIndex          块的索引
     * @param sliceCache          缓存的块和片的信息
     * @param tag                 用于标记分片上传，可取任意值(比如说文件名),只对取消产生影响，不想使用可置为null
     * @param progressNotifier    进度提示
     * @param uploadBlockListener 块上传回调
     * @param byteArray           存放当前片
     * @param conf                一些自定义参数
     */
    private static void uploadBlock(final Context context,
                                    final String uploadToken, final Block block, final int blockIndex,
                                    final SliceCache sliceCache,
                                    final Object tag, final ProgressNotifier progressNotifier,
                                    final UploadBlockListener uploadBlockListener,
                                    final ByteArray byteArray,
                                    final ParamsConf conf) {


        //设置缓存
        block.setByteArray(byteArray);
        final int currentIndex = block.getIndex();
        Slice slice = block.moveToNext();
        if (null != slice && currentIndex == 0) { //  第一片
            makeBlock(tag, context, uploadToken, block, blockIndex, slice, sliceCache, progressNotifier, uploadBlockListener, conf);
        } else if (null != slice) {
            uploadSlice(tag, context, uploadToken, block, blockIndex, slice,
                    sliceCache, sliceCache.getBlockContext().get(blockIndex), progressNotifier,
                    uploadBlockListener, conf);
        } else {//分片已经完成
//            block.releaseBuffer();//释放缓存
            uploadBlockListener.onBlockUploaded(blockIndex, sliceCache.getBlockContext().get(blockIndex));
        }
    }

    /**
     * 生成一个block，同时上传第一片
     *
     * @param tag                 用于标记分片上传，可取任意值(比如说文件名),只对取消产生影响，不想使用可置为null
     * @param context             上下文
     * @param uploadToken         上传token
     * @param block               块
     * @param blockIndex          块的索引
     * @param slice               当前片
     * @param sliceCache          缓存的块和片的信息
     * @param progressNotifier    进度提示
     * @param uploadBlockListener 块上传回调
     * @param conf                一些自定义参数
     */
    private static void makeBlock(final Object tag, final Context context, final String uploadToken, final Block block, final int blockIndex, final Slice slice, final SliceCache sliceCache, final ProgressNotifier progressNotifier, final UploadBlockListener uploadBlockListener, final ParamsConf conf) {
        WcsCompletedCallback<WcsRequest, SliceResponse> wcsCompletedCallback =