All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.upyun.ParallelUploader Maven / Gradle / Ivy

package com.upyun;

import okhttp3.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.*;

public class ParallelUploader {

    private static final String AUTHORIZATION = "Authorization";
    private static final int BLOCK_SIZE = 1024 * 1024;

    private final String DATE = "Date";

    private static final String CONTENT_MD5 = "Content-MD5";
    private static final String CONTENT_TYPE = "CContent-Type";
    private static final String CONTENT_SECRET = "Content-Secret";
    private static final String X_Upyun_Meta_X = "X-Upyun-Meta-X";

    private static final String X_UPYUN_MULTI_DISORDER = "X-Upyun-Multi-Disorder";
    private static final String X_UPYUN_MULTI_STAGE = "X-Upyun-Multi-Stage";
    private static final String X_UPYUN_MULTI_TYPE = "X-Upyun-Multi-Type";
    private static final String X_UPYUN_MULTI_LENGTH = "X-Upyun-Multi-Length";
    private static final String X_UPYUN_MULTI_UUID = "X-Upyun-Multi-UUID";
    private static final String X_UPYUN_PART_ID = "X-Upyun-Part-ID";
    private static final String HOST = "https://v0.api.upyun.com";

    private String uuid;
    private String uploadPath;
    private OkHttpClient mClient;
    private File mFile;
    private RandomAccessFile randomAccessFile;

    private boolean checkMD5;

    // 空间名
    protected String bucketName = null;
    // 操作员名
    protected String userName = null;
    // 操作员密码
    protected String password = null;
    //超时设置(s)
    private int timeout = 20;

    private String url;

    private ResumeUploader.OnProgressListener onProgressListener;

    private int totalBlock;

    private volatile int blockProgress;

    public void setParalle(int paralle) {
        this.paralle = paralle;
    }

    //并行式断点并行数
    private int paralle = 4;

    public void setRetryTime(int retryTime) {
        this.retryTime = retryTime;
    }

    //并行式断点分块重试册数
    private int retryTime = 2;

    /**
     * 初始化 ResumeUploader
     *
     * @param bucketName 空间名称
     * @param userName   操作员名称
     * @param password   密码,需要MD5加密
     * @return ResumeUploader object
     */
    public ParallelUploader(String bucketName, String userName, String password) {
        this.bucketName = bucketName;
        this.userName = userName;
        this.password = UpYunUtils.md5(password);
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * 开始上传
     *
     * @param filePath   本地上传文件路径
     * @param uploadPath 上传服务器路径
     * @param params     通用上传参数(见 rest api 文档)
     * @return 是否上传成功
     * @throws IOException
     */
    public boolean upload(String filePath, String uploadPath, Map params) throws IOException, UpException, ExecutionException, InterruptedException {

        this.mFile = new File(filePath);

        this.totalBlock = (int) Math.ceil(mFile.length() / (double) BLOCK_SIZE + 2);

        this.randomAccessFile = new RandomAccessFile(mFile, "r");

        this.uploadPath = uploadPath;

        this.url = HOST + "/" + bucketName + uploadPath;

        this.mClient = new OkHttpClient.Builder()
                .connectTimeout(timeout, TimeUnit.SECONDS)
                .readTimeout(timeout, TimeUnit.SECONDS)
                .writeTimeout(timeout, TimeUnit.SECONDS)
                .build();

        return startUpload(params);
    }


    /**
     * 获取 uuid
     *
     * @return uuid
     */
    public String getUuid() {
        return uuid;
    }


    /**
     * 设置是否 MD5 校验
     *
     * @param checkMD5 是否 MD5 校验
     */
    public void setCheckMD5(boolean checkMD5) {
        this.checkMD5 = checkMD5;
    }

    /**
     * 设置上传进度监听
     *
     * @param onProgressListener 上传进度 listener
     */
    public void setOnProgressListener(ResumeUploader.OnProgressListener onProgressListener) {
        this.onProgressListener = onProgressListener;
    }

    private boolean startUpload(Map params) throws IOException, UpException, ExecutionException, InterruptedException {

        if (uuid != null) {
            return processUpload();
        }

        RequestBody requestBody = RequestBody.create(null, "");

        String date = getGMTDate();

        String md5 = null;

        if (checkMD5) {
            md5 = UpYunUtils.md5("");
        }

        String sign = sign("PUT", date, "/" + bucketName + uploadPath, userName, password, md5).trim();

        Request.Builder builder = new Request.Builder()
                .url(url)
                .header(DATE, date)
                .header(AUTHORIZATION, sign)
                .header(X_UPYUN_MULTI_DISORDER, "true")
                .header(X_UPYUN_MULTI_STAGE, "initiate")
                .header(X_UPYUN_MULTI_TYPE, "application/octet-stream")
                .header(X_UPYUN_MULTI_LENGTH, mFile.length() + "")
                .header("User-Agent", UpYunUtils.VERSION)
                .put(requestBody);

        if (params != null) {
            for (Map.Entry entry : params.entrySet()) {
                builder.header(entry.getKey(), entry.getValue());
            }
        }

        if (md5 != null) {
            builder.header(CONTENT_MD5, md5);
        }
        callRequest(builder.build(), 1);

        return processUpload();
    }

    private boolean processUpload() throws IOException, UpException, InterruptedException, ExecutionException {

        blockProgress = 0;

        ExecutorService uploadExecutor = Executors.newFixedThreadPool(paralle);

        for (int i = 0; i < totalBlock - 2; i++) {
            Future future = uploadExecutor.submit(uploadBlock(i));

            try {
                future.get();
            } catch (InterruptedException e) {
                throw e;
            } catch (ExecutionException e) {
                throw e;
            }
        }

        uploadExecutor.shutdown();

        try {//等待直到所有任务完成
            uploadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (randomAccessFile != null) {
            randomAccessFile.close();
            randomAccessFile = null;
        }

        return completeUpload();
    }

    private Runnable uploadBlock(final int index) {
        return new Runnable() {
            public void run() {

                try {
                    byte[] data = readBlockByIndex(index);

                    int retry = 0;

                    RequestBody requestBody = RequestBody.create(null, data);

                    String date = getGMTDate();

                    String md5 = null;

                    if (checkMD5) {
                        md5 = UpYunUtils.md5(data);
                    }

                    String sign = sign("PUT", date, "/" + bucketName + uploadPath, userName, password, md5).trim();

                    Request.Builder builder = new Request.Builder()
                            .url(url)
                            .header(DATE, date)
                            .header(AUTHORIZATION, sign)
                            .header(X_UPYUN_MULTI_STAGE, "upload")
                            .header(X_UPYUN_MULTI_UUID, uuid)
                            .header(X_UPYUN_PART_ID, index + "")
                            .header("User-Agent", UpYunUtils.VERSION)
                            .put(requestBody);

                    if (md5 != null) {
                        builder.header(CONTENT_MD5, md5);
                    }

                    Response response = uploadRequest(retry, builder);

                    uuid = response.header(X_UPYUN_MULTI_UUID, "");

                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        };

    }

    private Response uploadRequest(int retry, Request.Builder builder) {

        try {
            Response response = mClient.newCall(builder.build()).execute();
            if (!response.isSuccessful() && retry < retryTime) {
                return uploadRequest(++retry, builder);
            } else if (!response.isSuccessful() && retry >= retryTime) {
                throw new RuntimeException(response.body().string());
            } else {
                if (onProgressListener != null) {
                    onProgressListener.onProgress(blockProgress + 2, totalBlock);
                }
                blockProgress++;
            }
            return response;

        } catch (IOException e) {
            if (retry < retryTime) {
                return uploadRequest(++retry, builder);
            } else {
                throw new RuntimeException(e.toString());
            }
        }
    }

    private boolean completeUpload() throws IOException, UpException {

        RequestBody requestBody = RequestBody.create(null, "");

        String date = getGMTDate();

        String md5 = null;

        if (checkMD5) {
            md5 = UpYunUtils.md5("");
        }

        String sign = sign("PUT", date, "/" + bucketName + uploadPath, userName, password, md5).trim();

        Request.Builder builder = new Request.Builder()
                .url(url)
                .header(DATE, date)
                .header(AUTHORIZATION, sign)
                .header(X_UPYUN_MULTI_STAGE, "complete")
                .header(X_UPYUN_MULTI_UUID, uuid)
                .header("User-Agent", UpYunUtils.VERSION)
                .put(requestBody);

        if (md5 != null) {
            builder.header(CONTENT_MD5, md5);
        }

        callRequest(builder.build(), totalBlock);

        uuid = null;
        return true;
    }

    private void callRequest(Request request, int index) throws IOException, UpException {

        Response response = mClient.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new UpException(response.body().string());
        } else {
            if (onProgressListener != null) {
                onProgressListener.onProgress(index, totalBlock);
            }
        }

        uuid = response.header(X_UPYUN_MULTI_UUID, "");
    }

    /**
     * 获取 GMT 格式时间戳
     *
     * @return GMT 格式时间戳
     */
    private String getGMTDate() {
        SimpleDateFormat formater = new SimpleDateFormat(
                "EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        formater.setTimeZone(TimeZone.getTimeZone("GMT"));
        return formater.format(new Date());
    }

    private String sign(String method, String date, String path, String userName, String password, String md5) throws UpException {

        StringBuilder sb = new StringBuilder();
        String sp = "&";
        sb.append(method);
        sb.append(sp);
//        sb.append("/" + bucket + path);
        sb.append(path);

        sb.append(sp);
        sb.append(date);

        if (md5 != null) {
            sb.append(sp);
            sb.append(md5);
        }
        String raw = sb.toString().trim();
        byte[] hmac = null;
        try {
            hmac = UpYunUtils.calculateRFC2104HMACRaw(password, raw);
        } catch (Exception e) {
            throw new UpException("calculate SHA1 wrong.");
        }

        if (hmac != null) {
            return "UPYUN " + userName + ":" + Base64Coder.encodeLines(hmac);
        }

        return null;
    }

    private byte[] readBlockByIndex(int index) throws IOException {
        byte[] block = new byte[BLOCK_SIZE];
        int readedSize = 0;
        int offset = index * BLOCK_SIZE;
        randomAccessFile.seek(offset);
        readedSize = randomAccessFile.read(block, 0, BLOCK_SIZE);

        // read last block, adjust byte size
        if (readedSize < BLOCK_SIZE) {
            byte[] notFullBlock = new byte[readedSize];
            System.arraycopy(block, 0, notFullBlock, 0, readedSize);
            return notFullBlock;
        }
        return block;
    }

    public interface OnInterruptListener {
        void OnInterrupt(boolean interrupted);
    }

    public class Params {
        /**
         * 请求参数
         * 

* bucket_name string 是 文件所在服务名称(空间名称) * notify_url string 是 回调通知地址 * source string 是 待处理文件路径 * tasks string 是 处理任务信息,详见下 * accept string 是 必须指定为 json */ public final static String BUCKET_NAME = "bucket_name"; public final static String NOTIFY_URL = "notify_url"; public final static String SOURCE = "source"; public final static String TASKS = "tasks"; public final static String ACCEPT = "accept"; /** * 回调通知参数 *

* status_code integer 处理结果状态码,200 表示成功处理 * path array 输出文件保存路径 * description string 处理结果描述 * task_id string 任务对应的 task_id * info string 视频文件的元数据信息。经过 base64 处理过之后的 JSON 字符串,仅当 type 为 video且 return_info 为 true 时返回 * signature string 回调验证签名,用户端程序可以通过校验签名,判断回调通知的合法性 * timestamp integer 服务器回调此信息时的时间戳 */ public final static String STATUS_CODE = "status_code"; public final static String PATH = "path"; public final static String DESCRIPTION = "description"; public final static String TASK_ID = "task_id"; public final static String INFO = "info"; public final static String SIGNATURE = "signature"; public final static String TIMESTAMP = "timestamp"; /** * 查询参数 *

* task_ids string 任务 id 以 , 作为分隔符,最多 20 个 */ public final static String TASK_IDS = "task_ids"; /** * 处理通用参数 *

* type string 音视频处理类型。不同的处理任务对应不同的 type,详见下方各处理任务说明 * save_as string 输出文件保存路径(同一个空间下),如果没有指定,系统自动生成在同空间同目录下 * return_info boolean 是否返回 JSON 格式元数据,默认 false。支持 type 值为 video 功能 * avopts string 音视频处理参数, 格式为 /key/value/key/value/... */ public final static String TYPE = "type"; public final static String SAVE_AS = "save_as"; public final static String RETURN_INFO = "return_info"; public final static String AVOPTS = "avopts"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy