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;
  
  import android.os.SystemClock;
  import com.lidroid.xutils.HttpUtils;
  import com.lidroid.xutils.exception.HttpException;
  import com.lidroid.xutils.http.callback.*;
  import com.lidroid.xutils.task.PriorityAsyncTask;
  import com.lidroid.xutils.util.OtherUtils;
  import org.apache.http.HttpEntity;
  import org.apache.http.HttpResponse;
  import org.apache.http.ProtocolException;
  import org.apache.http.StatusLine;
  import org.apache.http.client.HttpRequestRetryHandler;
  import org.apache.http.client.RedirectHandler;
  import org.apache.http.client.methods.HttpRequestBase;
  import org.apache.http.impl.client.AbstractHttpClient;
  import org.apache.http.protocol.HttpContext;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.URI;
  import java.net.UnknownHostException;
  
  
  public class HttpHandler<T> extends PriorityAsyncTask<Object, Object, Void> implements RequestCallBackHandler {
  
      private final AbstractHttpClient client;
      private final HttpContext context;
  
      private HttpRedirectHandler httpRedirectHandler;
  
      public void setHttpRedirectHandler(HttpRedirectHandler httpRedirectHandler) {
          if (httpRedirectHandler != null) {
              this.httpRedirectHandler = httpRedirectHandler;
          }
      }
  
      private String requestUrl;
      private String requestMethod;
      private HttpRequestBase request;
      private boolean isUploading = true;
      private RequestCallBack<T> callback;
  
      private int retriedCount = 0;
      private String fileSavePath = null;
      private boolean isDownloadingFile = false;
      private boolean autoResume = false; // Whether the downloading could continue from the point of interruption.
      private boolean autoRename = false; // Whether rename the file by response header info when the download completely.
      private String charset; // The default charset of response header info.
  
      public HttpHandler(AbstractHttpClient client, HttpContext context, String charset, RequestCallBack<T> callback) {
          this.client = client;
          this.context = context;
          this.callback = callback;
          this.charset = charset;
          this.client.setRedirectHandler(notUseApacheRedirectHandler);
      }
  
      private State state = State.WAITING;
  
      public State getState() {
          return state;
      }
  
      private long expiry = HttpCache.getDefaultExpiryTime();
  
      public void setExpiry(long expiry) {
          this.expiry = expiry;
      }
  
      public void setRequestCallBack(RequestCallBack<T> callback) {
          this.callback = callback;
      }
  
      public RequestCallBack<T> getRequestCallBack() {
          return this.callback;
      }
  
      // 执行请求
      @SuppressWarnings("unchecked")
      private ResponseInfo<T> sendRequest(HttpRequestBase request) throws HttpException {
  
          HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
          while (true) {
  
              if (autoResume && isDownloadingFile) {
                  File downloadFile = new File(fileSavePath);
                  long fileLen = 0;
                  if (downloadFile.isFile() && downloadFile.exists()) {
                      fileLen = downloadFile.length();
                  }
                  if (fileLen > 0) {
                      request.setHeader("RANGE", "bytes=" + fileLen + "-"); //这里是断点下载的关键
                  }
              }
  
              boolean retry = true;
              IOException exception = null;
              try {
                  requestMethod = request.getMethod();
                  //主要是判断请求是否存在缓存， 存在直接从缓存中读取，直接返回。
                  if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
                      String result = HttpUtils.sHttpCache.get(requestUrl);
                      if (result != null) {
                          return new ResponseInfo<T>(null, (T) result, true);   
                      }
                  }
  
                  ResponseInfo<T> responseInfo = null;
                  if (!isCancelled()) {
                      HttpResponse response = client.execute(request, context);  //获取网络返回的数据
                      responseInfo = handleResponse(response); //将数据包装
                  }
                  return responseInfo;
              } catch (UnknownHostException e) {
                  exception = e;
                  retry = retryHandler.retryRequest(exception, ++retriedCount, context);  //重试
              } catch (IOException e) {
                  exception = e;
                  retry = retryHandler.retryRequest(exception, ++retriedCount, context);
              } catch (NullPointerException e) {
                  exception = new IOException(e.getMessage());
                  exception.initCause(e);
                  retry = retryHandler.retryRequest(exception, ++retriedCount, context);
              } catch (HttpException e) {
                  throw e;
              } catch (Throwable e) {
                  exception = new IOException(e.getMessage());
                  exception.initCause(e);
                  retry = retryHandler.retryRequest(exception, ++retriedCount, context);
              }
              if (!retry) {
                  throw new HttpException(exception);
              }
          }
      }
  
      @Override
      protected Void doInBackground(Object... params) {
          if (this.state == State.CANCELLED || params == null || params.length == 0) return null;
  
          //主要是判断是否是下载文件
          if (params.length > 3) {
              fileSavePath = String.valueOf(params[1]);
              isDownloadingFile = fileSavePath != null;
              autoResume = (Boolean) params[2];
              autoRename = (Boolean) params[3];
          }
  
          try {
              if (this.state == State.CANCELLED) return null;
              // init request & requestUrl
              request = (HttpRequestBase) params[0];
              requestUrl = request.getURI().toString();
              if (callback != null) {
                  callback.setRequestUrl(requestUrl);
              }
  
              this.publishProgress(UPDATE_START); //回调， 开始
  
              lastUpdateTime = SystemClock.uptimeMillis();
  
              ResponseInfo<T> responseInfo = sendRequest(request);
              if (responseInfo != null) {
                  this.publishProgress(UPDATE_SUCCESS, responseInfo); //回调， 成功
                  return null;
              }
          } catch (HttpException e) {
              this.publishProgress(UPDATE_FAILURE, e, e.getMessage()); //回调， 失败
          }
  
          return null;
      }
  
      private final static int UPDATE_START = 1;
      private final static int UPDATE_LOADING = 2;
      private final static int UPDATE_FAILURE = 3;
      private final static int UPDATE_SUCCESS = 4;
  
      /* (non-Javadoc)
       * @see com.lidroid.xutils.task.PriorityAsyncTask#onProgressUpdate(Progress[])
       *回调
       */
      @Override
      @SuppressWarnings("unchecked")
      protected void onProgressUpdate(Object... values) {
          if (this.state == State.CANCELLED || values == null || values.length == 0 || callback == null) return;
          switch ((Integer) values[0]) {
              case UPDATE_START:
                  this.state = State.STARTED;
                  callback.onStart();
                  break;
              case UPDATE_LOADING:
                  if (values.length != 3) return;
                  this.state = State.LOADING;
                  callback.onLoading(
                          Long.valueOf(String.valueOf(values[1])),
                          Long.valueOf(String.valueOf(values[2])),
                          isUploading);
                  break;
              case UPDATE_FAILURE:
                  if (values.length != 3) return;
                  this.state = State.FAILURE;
                  callback.onFailure((HttpException) values[1], (String) values[2]);
                  break;
              case UPDATE_SUCCESS:
                  if (values.length != 2) return;
                  this.state = State.SUCCESS;
                  callback.onSuccess((ResponseInfo<T>) values[1]);
                  break;
              default:
                  break;
          }
      }
  
      @SuppressWarnings("unchecked")
      private ResponseInfo<T> handleResponse(HttpResponse response) throws HttpException, IOException {
          if (response == null) {
              throw new HttpException("response is null");
          }
          if (isCancelled()) return null;
  
          StatusLine status = response.getStatusLine();
          int statusCode = status.getStatusCode();
          if (statusCode < 300) {
              Object result = null;
              HttpEntity entity = response.getEntity();
              if (entity != null) {
                  isUploading = false;
                  if (isDownloadingFile) {
                      autoResume = autoResume && OtherUtils.isSupportRange(response);
                      String responseFileName = autoRename ? OtherUtils.getFileNameFromHttpResponse(response) : null;
                      FileDownloadHandler downloadHandler = new FileDownloadHandler();
                      result = downloadHandler.handleEntity(entity, this, fileSavePath, autoResume, responseFileName);
                  } else {
                      StringDownloadHandler downloadHandler = new StringDownloadHandler();
                      result = downloadHandler.handleEntity(entity, this, charset);
                      if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
                          HttpUtils.sHttpCache.put(requestUrl, (String) result, expiry);  //只有是字符串才会缓存，文件不缓存， 否则OOM
                      }
                  }
              }
              return new ResponseInfo<T>(response, (T) result, false);
          } else if (statusCode == 301 || statusCode == 302) {
              if (httpRedirectHandler == null) {
                  httpRedirectHandler = new DefaultHttpRedirectHandler();
              }
              HttpRequestBase request = httpRedirectHandler.getDirectRequest(response);
              if (request != null) {
                  return this.sendRequest(request);
              }
          } else if (statusCode == 416) {
              throw new HttpException(statusCode, "maybe the file has downloaded completely");
          } else {
              throw new HttpException(statusCode, status.getReasonPhrase());
          }
          return null;
      }
  
      /**
       * cancel request task.
       */
      @Override
      public void cancel() {
          this.state = State.CANCELLED;
  
          if (request != null && !request.isAborted()) {
              try {
                  request.abort();
              } catch (Throwable e) {
              }
          }
          if (!this.isCancelled()) {
              try {
                  this.cancel(true);
              } catch (Throwable e) {
              }
          }
  
          if (callback != null) {
              callback.onCancelled();
          }
      }
  
      private long lastUpdateTime;
  
      @Override
      public boolean updateProgress(long total, long current, boolean forceUpdateUI) {
          if (callback != null && this.state != State.CANCELLED) {
              if (forceUpdateUI) {
                  this.publishProgress(UPDATE_LOADING, total, current);
              } else {
                  long currTime = SystemClock.uptimeMillis();
                  if (currTime - lastUpdateTime >= callback.getRate()) {
                      lastUpdateTime = currTime;
                      this.publishProgress(UPDATE_LOADING, total, current);
                  }
              }
          }
          return this.state != State.CANCELLED;
      }
  
      public enum State {
          WAITING(0), STARTED(1), LOADING(2), FAILURE(3), CANCELLED(4), SUCCESS(5);
          private int value = 0;
  
          State(int value) {
              this.value = value;
          }
  
          public static State valueOf(int value) {
              switch (value) {
                  case 0:
                      return WAITING;
                  case 1:
                      return STARTED;
                  case 2:
                      return LOADING;
                  case 3:
                      return FAILURE;
                  case 4:
                      return CANCELLED;
                  case 5:
                      return SUCCESS;
                  default:
                      return FAILURE;
              }
          }
  
          public int value() {
              return this.value;
          }
      }
  
      private static final NotUseApacheRedirectHandler notUseApacheRedirectHandler = new NotUseApacheRedirectHandler();
  
      private static final class NotUseApacheRedirectHandler implements RedirectHandler {
          @Override
          public boolean isRedirectRequested(HttpResponse httpResponse, HttpContext httpContext) {
              return false;
          }
  
          @Override
          public URI getLocationURI(HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException {
              return null;
          }
      }
  }
  