content: /*
   * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.lidroid.xutils.http.client.multipart;
  
  import com.lidroid.xutils.http.callback.RequestCallBackHandler;
  import com.lidroid.xutils.http.client.entity.UploadEntity;
  import com.lidroid.xutils.http.client.multipart.content.ContentBody;
  import org.apache.http.Header;
  import org.apache.http.HttpEntity;
  import org.apache.http.message.BasicHeader;
  import org.apache.http.protocol.HTTP;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.nio.charset.Charset;
  import java.util.Random;
  
  /**
   * Multipart/form coded HTTP entity consisting of multiple body parts.
   *
   * @since 4.0
   */
  public class MultipartEntity implements HttpEntity, UploadEntity {
  
      private CallBackInfo callBackInfo = new CallBackInfo();
  
      @Override
      public void setCallBackHandler(RequestCallBackHandler callBackHandler) {
          callBackInfo.callBackHandler = callBackHandler;
      }
  
      // wyouflf add： upload callBackHandler
      public static class CallBackInfo {
          public final static CallBackInfo DEFAULT = new CallBackInfo();
          public RequestCallBackHandler callBackHandler = null;
          public long totalLength = 0;
          public long pos = 0;
  
          /**
           * @param forceUpdateUI
           * @return Whether continue.
           */
          public boolean doCallBack(boolean forceUpdateUI) {
              if (callBackHandler != null) {
                  return callBackHandler.updateProgress(totalLength, pos, forceUpdateUI);
              }
              return true;
          }
      }
  
      /**
       * The pool of ASCII chars to be used for generating a multipart boundary.
       */
      private final static char[] MULTIPART_CHARS =
              "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
                      .toCharArray();
  
      private final HttpMultipart multipart;
      private Header contentType;
  
      // @GuardedBy("dirty") // we always read dirty before accessing length
      private long length;
      private volatile boolean dirty; // used to decide whether to recalculate length
  
      // wyouflf add
      private final String boundary;
      private final Charset charset;
  
      /**
       * Creates an instance using the specified parameters
       *
       * @param mode     the mode to use, may be {@code null}, in which case {@link HttpMultipartMode#STRICT} is used
       * @param boundary the boundary string, may be {@code null}, in which case {@link #generateBoundary()} is invoked to create the string
       * @param charset  the character set to use, may be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. UTF-8 - is used.
       */
      public MultipartEntity(
              HttpMultipartMode mode,
              String boundary,
              Charset charset) {
          super();
          if (boundary == null) {
              boundary = generateBoundary();
          }
          this.boundary = boundary;
          if (mode == null) {
              mode = HttpMultipartMode.STRICT;
          }
          this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
          this.multipart = new HttpMultipart(multipartSubtype, this.charset, this.boundary, mode);
          this.contentType = new BasicHeader(
                  HTTP.CONTENT_TYPE,
                  generateContentType(this.boundary, this.charset));
          this.dirty = true;
      }
  
      /**
       * Creates an instance using the specified {@link HttpMultipartMode} mode.
       * Boundary and charset are set to {@code null}.
       *
       * @param mode the desired mode
       */
      public MultipartEntity(final HttpMultipartMode mode) {
          this(mode, null, null);
      }
  
      /**
       * Creates an instance using mode {@link HttpMultipartMode#STRICT}
       */
      public MultipartEntity() {
          this(HttpMultipartMode.STRICT, null, null);
      }
  
      // wyouflf add
      private String multipartSubtype = "form-data";
  
      /**
       * @param multipartSubtype default "form-data"
       */
      public void setMultipartSubtype(String multipartSubtype) {
          this.multipartSubtype = multipartSubtype;
          this.multipart.setSubType(multipartSubtype);
          this.contentType = new BasicHeader(
                  HTTP.CONTENT_TYPE,
                  generateContentType(this.boundary, this.charset));
      }
  
      protected String generateContentType(
              final String boundary,
              final Charset charset) {
          StringBuilder buffer = new StringBuilder();
          buffer.append("multipart/" + multipartSubtype + "; boundary=");
          buffer.append(boundary);
          /*if (charset != null) {
              buffer.append("; charset=");
              buffer.append(charset.name());
          }*/
          return buffer.toString();
      }
  
      protected String generateBoundary() {
          StringBuilder buffer = new StringBuilder();
          Random rand = new Random();
          int count = rand.nextInt(11) + 30; // a random size from 30 to 40
          for (int i = 0; i < count; i++) {
              buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
          }
          return buffer.toString();
      }
  
      public void addPart(final FormBodyPart bodyPart) {
          this.multipart.addBodyPart(bodyPart);
          this.dirty = true;
      }
  
      public void addPart(final String name, final ContentBody contentBody) {
          addPart(new FormBodyPart(name, contentBody));
      }
  
      public void addPart(final String name, final ContentBody contentBody, final String contentDisposition) {
          addPart(new FormBodyPart(name, contentBody, contentDisposition));
      }
  
      public boolean isRepeatable() {
          for (FormBodyPart part : this.multipart.getBodyParts()) {
              ContentBody body = part.getBody();
              if (body.getContentLength() < 0) {
                  return false;
              }
          }
          return true;
      }
  
      public boolean isChunked() {
          return !isRepeatable();
      }
  
      public boolean isStreaming() {
          return !isRepeatable();
      }
  
      public long getContentLength() {
          if (this.dirty) {
              this.length = this.multipart.getTotalLength();
              this.dirty = false;
          }
          return this.length;
      }
  
      public Header getContentType() {
          return this.contentType;
      }
  
      public Header getContentEncoding() {
          return null;
      }
  
      public void consumeContent()
              throws IOException, UnsupportedOperationException {
          if (isStreaming()) {
              throw new UnsupportedOperationException(
                      "Streaming entity does not implement #consumeContent()");
          }
      }
  
      public InputStream getContent() throws IOException, UnsupportedOperationException {
          throw new UnsupportedOperationException(
                  "Multipart form entity does not implement #getContent()");
      }
  
      public void writeTo(final OutputStream outStream) throws IOException {
          callBackInfo.totalLength = getContentLength();
          this.multipart.writeTo(outStream, callBackInfo);
      }
  }
  