package com.qcloud.cos.op;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.common_utils.CommonCodecUtils;
import com.qcloud.cos.common_utils.CommonFileUtils;
import com.qcloud.cos.common_utils.CommonPathUtils;
import com.qcloud.cos.exception.AbstractCosException;
import com.qcloud.cos.exception.ParamException;
import com.qcloud.cos.exception.UnknownException;
import com.qcloud.cos.http.AbstractCosHttpClient;
import com.qcloud.cos.http.HttpContentType;
import com.qcloud.cos.http.HttpMethod;
import com.qcloud.cos.http.HttpRequest;
import com.qcloud.cos.http.RequestBodyKey;
import com.qcloud.cos.http.RequestBodyValue;
import com.qcloud.cos.http.RequestHeaderKey;
import com.qcloud.cos.http.RequestHeaderValue;
import com.qcloud.cos.http.ResponseBodyKey;
import com.qcloud.cos.meta.InsertOnly;
import com.qcloud.cos.meta.SliceFileDataTask;
import com.qcloud.cos.meta.UploadSliceFileContext;
import com.qcloud.cos.request.DelFileRequest;
import com.qcloud.cos.request.GetFileInputStreamRequest;
import com.qcloud.cos.request.GetFileLocalRequest;
import com.qcloud.cos.request.ListPartsRequest;
import com.qcloud.cos.request.MoveFileRequest;
import com.qcloud.cos.request.StatFileRequest;
import com.qcloud.cos.request.UpdateFileRequest;
import com.qcloud.cos.request.UploadFileRequest;
import com.qcloud.cos.request.UploadSliceFileRequest;
import com.qcloud.cos.sign.Credentials;
import com.qcloud.cos.sign.Sign;

/**
 * @author chengwu 此类封装了文件操作
 */
public class FileOp extends BaseOp {

    private static final Logger LOG = LoggerFactory.getLogger(FileOp.class);

    public FileOp(ClientConfig config, Credentials cred, AbstractCosHttpClient client) {
        super(config, cred, client);
    }

    private String buildGetFileUrl(GetFileInputStreamRequest request) throws AbstractCosException {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(this.config.getDownCosEndPointPrefix()).append(request.getBucketName())
                .append("-").append(this.cred.getAppId()).append(".");
        if (request.isUseCDN()) {
            strBuilder.append("file.myqcloud.com");
        } else {
            strBuilder.append(this.config.getDownCosEndPointDomain());
        }
        strBuilder.append(CommonPathUtils.encodeRemotePath(request.getCosPath()));
        String url = strBuilder.toString();
        return url;
    }

    /**
     * 更新文件属性请求
     *
     * @param request 更新文件属性请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String updateFile(final UpdateFileRequest request) throws AbstractCosException {
        request.check_param();

        String url = buildUrl(request);
        String sign =
                Sign.getOneEffectiveSign(request.getBucketName(), request.getCosPath(), this.cred);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.Content_TYPE, RequestHeaderValue.ContentType.JSON);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.UPDATE);
        int updateFlag = request.getUpdateFlag();
        if ((updateFlag & 0x01) != 0) {
            httpRequest.addParam(RequestBodyKey.BIZ_ATTR, request.getBizAttr());
        }
        if ((updateFlag & 0x40) != 0) {
            String customHeaderStr = new JSONObject(request.getCustomHeaders()).toString();
            httpRequest.addParam(RequestBodyKey.CUSTOM_HEADERS, customHeaderStr);
        }
        if ((updateFlag & 0x80) != 0) {
            httpRequest.addParam(RequestBodyKey.AUTHORITY, request.getAuthority().toString());
        }
        httpRequest.setMethod(HttpMethod.POST);
        httpRequest.setContentType(HttpContentType.APPLICATION_JSON);
        return httpClient.sendHttpRequest(httpRequest);
    }

    /**
     * 删除文件请求
     *
     * @param request 删除文件请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String delFile(DelFileRequest request) throws AbstractCosException {
        return super.delBase(request);
    }

    /**
     * 获取文件属性请求
     *
     * @param request 获取文件属性请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String statFile(StatFileRequest request) throws AbstractCosException {
        return super.statBase(request);
    }

    /**
     * 上传文件请求, 对小文件(8MB以下使用单文件上传接口), 大文件使用分片上传接口
     *
     * @param request 上传文件请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String uploadFile(UploadFileRequest request) throws AbstractCosException {
        request.check_param();

        String localPath = request.getLocalPath();
        long fileSize = 0;
        if (request.isUploadFromBuffer()) {
            fileSize = request.getContentBufer().length;
        } else {
            try {
                fileSize = CommonFileUtils.getFileLength(localPath);
            } catch (Exception e) {
                throw new UnknownException(e.toString());
            }
        }

        long suitSingleFileSize = 8 * 1024 * 1024;
        if (fileSize < suitSingleFileSize) {
            return uploadSingleFile(request);
        } else {
            UploadSliceFileRequest sliceRequest = new UploadSliceFileRequest(request);
            sliceRequest.setInsertOnly(request.getInsertOnly());
            if (request.isUploadFromBuffer()) {
                sliceRequest.setContentBufer(request.getContentBufer());
            }
            sliceRequest.setEnableShaDigest(request.isEnableShaDigest());
            sliceRequest.setTaskNum(request.getTaskNum());
            return uploadSliceFile(sliceRequest);
        }
    }

    /**
     * 上传单文件请求, 不分片
     *
     * @param request 上传文件请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String uploadSingleFile(UploadFileRequest request) throws AbstractCosException {
        request.check_param();

        String localPath = request.getLocalPath();
        long fileSize = 0;
        if (request.isUploadFromBuffer()) {
            fileSize = request.getContentBufer().length;
        } else {
            try {
                fileSize = CommonFileUtils.getFileLength(localPath);
            } catch (Exception e) {
                throw new UnknownException(e.toString());
            }
        }
        // 单文件上传上限不超过20MB
        if (fileSize > 20 * 1024 * 1024) {
            throw new ParamException("file is to big, please use uploadFile interface!");
        }

        String fileContent = "";
        String shaDigest = "";
        try {
            if (request.isUploadFromBuffer()) {
                fileContent = new String(request.getContentBufer(), Charset.forName("ISO-8859-1"));
                shaDigest = CommonCodecUtils.getBufferSha1(request.getContentBufer());
            } else {
                fileContent = CommonFileUtils.getFileContent(localPath);
                shaDigest = CommonCodecUtils.getEntireFileSha1(localPath);
            }
        } catch (Exception e) {
            throw new UnknownException(e.toString());
        }

        String url = buildUrl(request);
        long signExpired = System.currentTimeMillis() / 1000 + this.config.getSignExpired();
        String sign = Sign.getPeriodEffectiveSign(request.getBucketName(), request.getCosPath(),
                this.cred, signExpired);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());

        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.UPLOAD);
        httpRequest.addParam(RequestBodyKey.SHA, shaDigest);
        httpRequest.addParam(RequestBodyKey.BIZ_ATTR, request.getBizAttr());
        httpRequest.addParam(RequestBodyKey.FILE_CONTENT, fileContent);
        httpRequest.addParam(RequestBodyKey.INSERT_ONLY,
                String.valueOf(request.getInsertOnly().ordinal()));

        httpRequest.setMethod(HttpMethod.POST);
        httpRequest.setContentType(HttpContentType.MULTIPART_FORM_DATA);

        String retStr = httpClient.sendHttpRequest(httpRequest);
        if (request.getInsertOnly() != InsertOnly.OVER_WRITE) {
            return retStr;
        }
        // 对于Overwrite类型,覆盖上传失败,做特殊处理,删掉重新传
        JSONObject retJson = new JSONObject(retStr);
        if (retJson.getInt("code") == 0) {
            return retStr;
        }
        // 1. Delete
        DelFileRequest del_request =
                new DelFileRequest(request.getBucketName(), request.getCosPath());
        String delRet = delFile(del_request);
        JSONObject delJson = new JSONObject(delRet);
        if (delJson.getInt("code") != 0) {
            return retStr;
        }
        // 2. Upload Again
        return httpClient.sendHttpRequest(httpRequest);
    }

    /**
     * 分片上传文件
     *
     * @param request 分片上传请求
     * @return 服务器端返回的操作结果,成员code为0表示成功,具体参照文档手册
     * @throws Exception
     */
    public String uploadSliceFile(UploadSliceFileRequest request) throws AbstractCosException {
        request.check_param();
        UploadSliceFileContext context = new UploadSliceFileContext(request);
        context.setUrl(buildUrl(request));
        String retStr = uploadFileWithCheckPoint(context);

        if (request.getInsertOnly() != InsertOnly.OVER_WRITE) {
            return retStr;
        }
        // 对于Overwrite类型,覆盖上传失败,做特殊处理,删掉重新传
        JSONObject retJson = new JSONObject(retStr);
        if (retJson.getInt("code") == 0) {
            return retStr;
        }
        // 1. Delete
        DelFileRequest del_request =
                new DelFileRequest(request.getBucketName(), request.getCosPath());
        String delRet = delFile(del_request);
        JSONObject delJson = new JSONObject(delRet);
        if (delJson.getInt("code") != 0) {
            return retStr;
        }
        // 2. Upload Again
        retStr = uploadFileWithCheckPoint(context);
        retJson = new JSONObject(retStr);
        if (retJson.getInt("code") != 0) {
            del_request = new DelFileRequest(request.getBucketName(), request.getCosPath());
            delFile(del_request);
        }
        return retStr;
    }

    /**
     * 移动文件请求(重命名)
     *
     * @param request 移动文件请求
     * @return JSON格式的字符串, 格式为{"code":$code, "message":"$mess"}, code为0表示成功, 其他为失败,
     *         message为success或者失败原因
     * @throws AbstractCosException SDK定义的COS异常, 通常是输入参数有误或者环境问题(如网络不通)
     */
    public String moveFile(MoveFileRequest request) throws AbstractCosException {
        request.check_param();

        String url = buildUrl(request);
        String sign =
                Sign.getOneEffectiveSign(request.getBucketName(), request.getCosPath(), this.cred);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.Content_TYPE, RequestHeaderValue.ContentType.JSON);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.MOVE);
        httpRequest.addParam(RequestBodyKey.DEST_FIELD, request.getDstCosPath());
        httpRequest.addParam(RequestBodyKey.TO_OVER_WRITE,
                String.valueOf(request.getOverWrite().ordinal()));
        httpRequest.setMethod(HttpMethod.POST);
        httpRequest.setContentType(HttpContentType.APPLICATION_JSON);
        return httpClient.sendHttpRequest(httpRequest);
    }

    // 断点续传
    private String uploadFileWithCheckPoint(UploadSliceFileContext context)
            throws AbstractCosException {
        JSONObject initResult = sendSliceInit(context);

        int initResultCode = initResult.getInt(ResponseBodyKey.CODE);
        // 4019文件未上传完成,这时候应该用断点续传
        if (initResultCode != 0 && initResultCode != -4019) {
            return initResult.toString();
        }

        if (initResultCode == 0) {
            JSONObject data = initResult.getJSONObject(ResponseBodyKey.DATA);
            if (data.has(ResponseBodyKey.Data.ACCESS_URL)) {
                return initResult.toString();
            }
            if (data.has(ResponseBodyKey.Data.SERIAL_UPLOAD)
                    && data.getInt(ResponseBodyKey.Data.SERIAL_UPLOAD) == 1) {
                LOG.debug("SERIAL_UPLOAD is true");
                context.setSerialUpload(true);
            } else {
                LOG.debug("SERIAL_UPLOAD is false");;
                context.setSerialUpload(false);
            }
            context.setSessionId(data.getString(ResponseBodyKey.Data.SESSION));
            // 如果服务端返回的slice_slize和用户要求的不一致, 则重新分片
            if (data.getInt(ResponseBodyKey.Data.SLICE_SIZE) != context.getSliceSize()) {
                context.setSliceSize(data.getInt(ResponseBodyKey.Data.SLICE_SIZE));
            }
        } else {
            ListPartsRequest listPartsRequest =
                    new ListPartsRequest(context.getBucketName(), context.getCosPath());
            String listPartsResult = sliceListParts(listPartsRequest);
            JSONObject listPartsJson = new JSONObject(listPartsResult);
            if (listPartsJson.getInt(ResponseBodyKey.CODE) != 0) {
                return listPartsJson.toString();
            }

            JSONObject data = listPartsJson.getJSONObject(ResponseBodyKey.DATA);
            if (data.has(ResponseBodyKey.Data.LISTPARTS)) {
                JSONArray listPartsJsonArry = data.getJSONArray(ResponseBodyKey.Data.LISTPARTS);
                context.setUploadCompleteParts(listPartsJsonArry);
            }
            context.setSessionId(data.getString(ResponseBodyKey.Data.SESSION));
        }
        
        context.prepareUploadPartsInfo();
        // 并行发送数据分片
        JSONObject sendParallelRet = sendSliceDataParallel(context);
        if (sendParallelRet.getInt(ResponseBodyKey.CODE) != 0) {
            return sendParallelRet.toString();
        }

        // 发送finish分片
        JSONObject finishRet = sendSliceFinish(context);
        return finishRet.toString();
    }

    /**
     * 分片上传第一步,发送init分片
     *
     * @param context 分片上传请求上下文
     * @return 服务器端返回的操作结果,code为0表示成功,其他包括sessionId、offset等,具体参见文档手册
     * @throws Exception
     */
    private JSONObject sendSliceInit(UploadSliceFileContext context) throws AbstractCosException {
        String localPath = context.getLocalPath();
        long fileSize = context.getFileSize();
        int sliceSize = context.getSliceSize();

        StringBuilder entireDigestSb = new StringBuilder();
        String slicePartDigest = "";
        try {
            if (context.isEnableShaDigest()) {
                if (context.isUploadFromBuffer()) {
                    slicePartDigest = CommonCodecUtils.getSlicePartSha1(context.getContentBuffer(),
                            sliceSize, entireDigestSb);
                } else {
                    slicePartDigest =
                            CommonCodecUtils.getSlicePartSha1(localPath, sliceSize, entireDigestSb);
                }
                context.setEntireFileSha(entireDigestSb.toString());
                LOG.debug("slicePartDigest: " + slicePartDigest);
            }
        } catch (Exception e) {
            throw new UnknownException(e.getMessage());
        }

        String url = context.getUrl();
        long signExpired = System.currentTimeMillis() / 1000 + this.config.getSignExpired();
        String sign = Sign.getPeriodEffectiveSign(context.getBucketName(), context.getCosPath(),
                this.cred, signExpired);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        httpRequest.addParam(RequestBodyKey.FILE_SIZE, String.valueOf(fileSize));
        httpRequest.addParam(RequestBodyKey.SLICE_SIZE, String.valueOf(sliceSize));
        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.UPLOAD_SLICE_INIT);
        httpRequest.addParam(RequestBodyKey.INSERT_ONLY,
                String.valueOf(context.getInsertOnly().ordinal()));
        httpRequest.addParam(RequestBodyKey.BIZ_ATTR, context.getBizAttr());
        if (context.isEnableShaDigest()) {
            httpRequest.addParam(RequestBodyKey.SHA, entireDigestSb.toString());
            httpRequest.addParam(RequestBodyKey.UPLOAD_PARTS, slicePartDigest);
        }

        httpRequest.setMethod(HttpMethod.POST);
        httpRequest.setContentType(HttpContentType.MULTIPART_FORM_DATA);

        JSONObject resultJson = null;
        String resultStr = this.httpClient.sendHttpRequest(httpRequest);
        LOG.debug("sendSliceInit, resultStr: " + resultStr);
        resultJson = new JSONObject(resultStr);
        return resultJson;
    }

    /**
     * 分片上传第二步,发送数据分片
     *
     * @param context 分片上传请求
     * @return 服务器端返回的操作结果,code为0表示成功
     * @throws Exception
     */
    private JSONObject sendSliceDataParallel(UploadSliceFileContext context)
            throws AbstractCosException {
        List<Future<JSONObject>> allSliceTasks = new ArrayList<Future<JSONObject>>();
        // 默认串行执行,只用一个线程,如果server端支持并行上传,则用多个线程执行
        int threadNum = 1;
        if (!context.isSerialUpload()) {
            threadNum = context.getTaskNum();
        }
        ExecutorService service = Executors.newFixedThreadPool(threadNum);

        String url = context.getUrl();
        long signExpired = this.config.getSignExpired();
        for (int sliceIndex = 0; sliceIndex < context.sliceParts.size(); ++sliceIndex) {
            if (!context.sliceParts.get(sliceIndex).isUploadCompleted()) {
                SliceFileDataTask dataTask = new SliceFileDataTask(sliceIndex, sliceIndex, context,
                        httpClient, cred, url, signExpired);
                allSliceTasks.add(service.submit(dataTask));
            }
        }
        service.shutdown();

        try {
            service.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
            service.shutdownNow();
        } catch (Exception e) {
            throw new UnknownException(e.getMessage());
        }

        JSONObject taskResult = null;
        if (allSliceTasks.size() == 0) {
            taskResult = new JSONObject();
            taskResult.put(ResponseBodyKey.CODE, 0);
            return taskResult;
        }

        for (Future<JSONObject> task : allSliceTasks) {
            try {
                taskResult = task.get();
            } catch (Exception e) {
                throw new UnknownException(e.getMessage());
            }
            if (taskResult.getInt(ResponseBodyKey.CODE) != 0) {
                return taskResult;
            }
        }

        return taskResult;
    }

    /**
     * 最后一步, 发送finish分片
     *
     * @return 服务器端返回的操作结果,成功code为0
     * @throws Exception
     */
    private JSONObject sendSliceFinish(UploadSliceFileContext context) throws AbstractCosException {
        String url = context.getUrl();

        long signExpired = System.currentTimeMillis() / 1000 + this.config.getSignExpired();
        String sign = Sign.getPeriodEffectiveSign(context.getBucketName(), context.getCosPath(),
                this.cred, signExpired);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        httpRequest.addParam(RequestBodyKey.SESSION, context.getSessionId());
        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.UPLOAD_SLICE_FINISH);
        if (context.isEnableShaDigest()) {
            httpRequest.addParam(RequestBodyKey.SHA, context.getEntireFileSha());
        }
        httpRequest.addParam(RequestBodyKey.FILE_SIZE, String.valueOf(context.getFileSize()));

        httpRequest.setContentType(HttpContentType.MULTIPART_FORM_DATA);
        httpRequest.setMethod(HttpMethod.POST);

        JSONObject resultJson = null;
        String resultStr = this.httpClient.sendHttpRequest(httpRequest);
        resultJson = new JSONObject(resultStr);
        LOG.debug("sendSliceFinish, resultStr: " + resultStr);
        return resultJson;
    }

    public String sliceListParts(ListPartsRequest request) throws AbstractCosException {
        request.check_param();

        String url = buildUrl(request);
        long signExpired = System.currentTimeMillis() / 1000 + this.config.getSignExpired();
        String sign = Sign.getPeriodEffectiveSign(request.getBucketName(), request.getCosPath(),
                this.cred, signExpired);

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.Authorization, sign);
        httpRequest.addHeader(RequestHeaderKey.Content_TYPE, RequestHeaderValue.ContentType.JSON);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        httpRequest.addParam(RequestBodyKey.OP, RequestBodyValue.OP.UPLOAD_SLICE_LIST);
        httpRequest.setMethod(HttpMethod.POST);
        httpRequest.setContentType(HttpContentType.APPLICATION_JSON);
        return httpClient.sendHttpRequest(httpRequest);
    }

    public String getFileLocal(GetFileLocalRequest request) throws AbstractCosException {
        InputStream in = getFileInputStream(request);
        BufferedInputStream bis = new BufferedInputStream(in);
        OutputStream out = null;
        try {
            out = new FileOutputStream(new File(request.getLocalPath()));
        } catch (FileNotFoundException e) {
            throw new UnknownException(e.getMessage());
        }
        BufferedOutputStream bos = new BufferedOutputStream(out);
        int inByte;
        try {
            while ((inByte = bis.read()) != -1)
                bos.write(inByte);
        } catch (IOException e) {
            throw new UnknownException(e.getMessage());
        } finally {
            try {
                bis.close();
                bos.close();
            } catch (IOException e) {
                throw new UnknownException(e.getMessage());
            }
        }
        JSONObject retJson = new JSONObject();
        retJson.put(ResponseBodyKey.CODE, 0);
        retJson.put(ResponseBodyKey.MESSAGE, "SUCCESS");
        return retJson.toString();
    }

    public InputStream getFileInputStream(GetFileInputStreamRequest request)
            throws AbstractCosException {
        String url = buildGetFileUrl(request);
        long signExpired = System.currentTimeMillis() / 1000 + this.config.getSignExpired();
        String sign = Sign.getDownLoadSign(request.getBucketName(), request.getCosPath(), this.cred,
                signExpired);

        StringBuilder rangeBuilder = new StringBuilder();
        if (request.getRangeStart() != 0 || request.getRangeEnd() != Long.MAX_VALUE) {
            rangeBuilder.append("bytes=").append(request.getRangeStart()).append("-");
            rangeBuilder.append(request.getRangeEnd());
        }

        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setUrl(url);
        httpRequest.addHeader(RequestHeaderKey.USER_AGENT, this.config.getUserAgent());
        if (!rangeBuilder.toString().isEmpty()) {
            httpRequest.addHeader(RequestHeaderKey.RANGE, rangeBuilder.toString());
        }
        if (!request.getReferer().isEmpty()) {
            httpRequest.addHeader(RequestHeaderKey.REFERER, request.getReferer());
        }
        httpRequest.addParam(RequestHeaderKey.SIGN, sign);
        httpRequest.setMethod(HttpMethod.GET);
        return httpClient.getFileInputStream(httpRequest);
    }
}