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

cn.leancloud.upload.QCloudUploader Maven / Gradle / Ivy

package cn.leancloud.upload;

import cn.leancloud.LCException;
import cn.leancloud.callback.ProgressCallback;
import cn.leancloud.codec.SHA1;
import cn.leancloud.LCFile;
import cn.leancloud.utils.StringUtil;
import cn.leancloud.json.JSON;
import cn.leancloud.json.JSONObject;
import okhttp3.*;

import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReferenceArray;

public class QCloudUploader extends HttpClientUploader {
  private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
  private static final String FILE_CONTENT = "filecontent";
  private static final String PARAM_OP = "op";
  private static final String PARAM_SHA = "sha";
  private static final String MULTIPART_FORM_DATA = "multipart/form-data";
  private static final String HEADER_AUTHORIZATION = "Authorization";
  private static final String HEADER_CONTENT_TYPE = "Content-Type";
  private static final String PARAM_FILE_SIZE = "filesize";
  private static final String PARAM_SLICE_SIZE = "slice_size";
  private static final String PARAM_OFFSET = "offset";
  private static final String PARAM_SESSION = "session";
  private static final String OP_UPLOAD_SLICE = "upload_slice";
  private static final String OP_UPLOAD = "upload";
  private static final String PARAM_ACCESS_URL = "access_url";
  private static final int RETRY_TIMES = 5;

  private AtomicReferenceArray tasks;
  private String fileSha;
  private String fileKey;
  private String uploadUrl;
  private String token;

  QCloudUploader(LCFile avFile, String token, String uploadUrl, ProgressCallback progressCallback) {
    super(avFile, progressCallback);

    this.fileKey = avFile.getKey();
    this.uploadUrl = uploadUrl;
    this.token = token;
  }

  private static final int DEFAULT_SLICE_LEN = 512 * 1024;

  public LCException execute() {
    try {
      byte[] bytes = avFile.getData();
      int sliceCount =
              (bytes.length / DEFAULT_SLICE_LEN) + (bytes.length % DEFAULT_SLICE_LEN == 0 ? 0 : 1);

      // 如果文件太小就没必要分片了
      if (sliceCount > 1) {
        JSONObject result = uploadControlSlice(token, uploadUrl, bytes);
        if (null == result) {
          return new LCException(new RuntimeException("Exception during file upload"));
        }
        if (result.containsKey(PARAM_ACCESS_URL)) {
          return null;
        }
        String sessionId = result.getString("session");

        FileUploader.ProgressCalculator progressCalculator =
                new FileUploader.ProgressCalculator(sliceCount, new FileUploader.FileUploadProgressCallback() {
                  @Override
                  public void onProgress(int progress) {
                    publishProgress(progress);
                  }
                });
        String sliceUploadResult = "";
        int sliceOffset = 0;
        for (; sliceOffset < sliceCount && null != sliceUploadResult; sliceOffset++) {
          sliceUploadResult = new SliceUploadTask(this, fileKey, token, uploadUrl,
                  bytes, sliceOffset, sessionId, progressCalculator, null).upload();
        }
        if (sliceOffset < sliceCount) {
          return new LCException(LCException.OTHER_CAUSE, "failed to upload slice.");
        }
      } else {
        uploadFile(bytes);
      }
    } catch (Exception e) {
      return new LCException(e);
    }

    return null;
  }

  private void uploadFile(byte[] bytes) throws LCException {

    try {
      fileSha = SHA1.compute(bytes);
      MultipartBody.Builder builder = new MultipartBody.Builder();
      RequestBody fileBody =
              RequestBody.create(MediaType.parse(APPLICATION_OCTET_STREAM), bytes, 0,
                      getCurrentSliceLength(0, bytes.length));
      builder.addFormDataPart(FILE_CONTENT, fileKey, fileBody);
      builder.addFormDataPart(PARAM_OP, OP_UPLOAD);
      builder.addFormDataPart(PARAM_SHA, fileSha);

      MediaType type = MediaType.parse(MULTIPART_FORM_DATA);
      if (null != type) {
        builder.setType(type);
      }

      Request.Builder requestBuilder = new Request.Builder();
      requestBuilder.url(uploadUrl);
      requestBuilder.header(HEADER_AUTHORIZATION, token);
      requestBuilder.header(HEADER_CONTENT_TYPE, MULTIPART_FORM_DATA);

      for (Map.Entry entry: FileUploader.UPLOAD_HEADERS.entrySet()) {
        requestBuilder.header(entry.getKey(), entry.getValue());
      }

      requestBuilder.post(builder.build());

      Request request = requestBuilder.build();
      Response response = executeWithRetry(request, RETRY_TIMES);
      if (response.code() != 200) {
        throw new LCException(LCException.OTHER_CAUSE,
                StringUtil.stringFromBytes(response.body().bytes()));
      }
    } catch (Exception e) {

      throw new LCException("Exception during file upload", e);
    }
  }

  private static JSONObject parseSliceUploadResponse(String resp) {
    if (!StringUtil.isEmpty(resp)) {
      try {
        JSONObject object = JSON.parseObject(resp);
        JSONObject data = object.getJSONObject("data");
        return data;
      } catch (Exception e) {
        ;
      }
    }
    return null;
  }

  private static int getCurrentSliceLength(int sliceCount, int totalSize) {
    int leftSize = totalSize - sliceCount * DEFAULT_SLICE_LEN;
    return leftSize >= DEFAULT_SLICE_LEN ? DEFAULT_SLICE_LEN : leftSize;
  }

  private JSONObject uploadControlSlice(String token, String url, byte[] wholeFile)
          throws LCException {
    MultipartBody.Builder builder = new MultipartBody.Builder();
    try {
      String fileSha = SHA1.compute(wholeFile);
      builder.addFormDataPart(PARAM_SHA, fileSha);
      builder.addFormDataPart(PARAM_OP, OP_UPLOAD_SLICE);
      builder.addFormDataPart(PARAM_FILE_SIZE, String.valueOf(wholeFile.length));
      builder.addFormDataPart(PARAM_SLICE_SIZE, String.valueOf(DEFAULT_SLICE_LEN));

      MediaType type = MediaType.parse(MULTIPART_FORM_DATA);
      if (null != type) {
        builder.setType(type);
      }

      Request.Builder requestBuilder = new Request.Builder();
      requestBuilder.url(url);
      requestBuilder.header(HEADER_AUTHORIZATION, token);
      requestBuilder.header(HEADER_CONTENT_TYPE, MULTIPART_FORM_DATA);
      requestBuilder.post(builder.build());

      Request request = requestBuilder.build();
      Response response = executeWithRetry(request, RETRY_TIMES);
      if (response != null) {
        byte[] responseBody = response.body().bytes();
        return parseSliceUploadResponse(StringUtil.stringFromBytes(responseBody));
      }
    } catch (Exception e) {
      throw new LCException(LCException.OTHER_CAUSE, "Upload file failure");
    }
    return null;
  }

  public static class SliceUploadTask implements Runnable {
    byte[] data;
    int sliceOffset;
    FileUploader.ProgressCalculator progress;
    String session;
    CountDownLatch latch;
    String token;
    String url;
    String key;
    QCloudUploader parent;

    public SliceUploadTask(QCloudUploader parent, String key, String token, String url, byte[] wholeFile, int sliceOffset,
                           String session, FileUploader.ProgressCalculator progressCalculator, CountDownLatch latch) {
      this.data = wholeFile;
      this.sliceOffset = sliceOffset;
      this.progress = progressCalculator;
      this.session = session;
      this.latch = latch;
      this.token = token;
      this.url = url;
      this.key = key;
      this.parent = parent;
    }

    public void run() {
      this.upload();
    }

    public String upload() {
      try {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        RequestBody fileBody =
                RequestBody.create(MediaType.parse(APPLICATION_OCTET_STREAM), data, sliceOffset
                                * DEFAULT_SLICE_LEN,
                        getCurrentSliceLength(sliceOffset, data.length));
        builder.addFormDataPart(FILE_CONTENT, key, fileBody);
        builder.addFormDataPart(PARAM_OP, OP_UPLOAD_SLICE);
        builder.addFormDataPart(PARAM_OFFSET,
                String.valueOf(sliceOffset * DEFAULT_SLICE_LEN));
        builder.addFormDataPart(PARAM_SESSION, session);

        MediaType type = MediaType.parse(MULTIPART_FORM_DATA);
        if (null != type) {
          builder.setType(type);
        }

        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(url);
        requestBuilder.header(HEADER_AUTHORIZATION, token);
        requestBuilder.header(HEADER_CONTENT_TYPE, MULTIPART_FORM_DATA);
        requestBuilder.post(builder.build());
        Request request = requestBuilder.build();
        Response response = parent.executeWithRetry(request, RETRY_TIMES);
        if (response != null && null != response.body()) {
          byte[] responseBody = response.body().bytes();
          if (progress != null) {
            progress.publishProgress(sliceOffset, 100);
          }
          if (null != responseBody) {
            return StringUtil.stringFromBytes(responseBody);
          }
        }
      } catch (Exception e) {
        if (latch != null) {
          long count = latch.getCount();
          for (; count > 0; count--) {
            latch.countDown();
          }
        }
      }
      return null;
    }
  }

  @Override
  public void interruptImmediately() {
    super.interruptImmediately();

    if (tasks != null && tasks.length() > 0) {
      synchronized (tasks) {
        for (int index = 0; index < tasks.length(); index++) {
          Future task = tasks.get(index);
          if (task != null && !task.isDone() && !task.isCancelled()) {
            task.cancel(true);
          }
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy