/**
   * Why pause? not stop? because invoke this method(pause) will clear all data about this task in
   * memory, and stop the total processing about this task. but when you start the paused task, it
   * would be continue downloading from the breakpoint as default.
   *
   * @return If true, successful pause this task by status of pause, otherwise this task has already
   *     in over status before invoke this method(Maybe occur high concurrent situation).
   * @see FileDownloader#pause(int)
   * @see FileDownloader#pause(FileDownloadListener)
   * @see FileDownloader#pauseAll()
   */
  public boolean pause() {
    if (FileDownloadStatus.isOver(getStatus())) {
      if (FileDownloadLog.NEED_LOG) {
        /**
         * The over-status call-backed and set the over-status to this task between here area and
         * remove from the {@link FileDownloadList}.
         *
         * <p>High concurrent cause.
         */
        FileDownloadLog.d(
            this,
            "High concurrent cause, Already is over, can't pause " + "again, %d %d",
            getStatus(),
            getId());
      }
      return false;
    }
    setStatus(FileDownloadStatus.paused);

    _pauseExecute();

    calcAverageSpeed(this.soFarBytes);
    // For make sure already added event listener for receive paused event
    FileDownloadList.getImpl().add(this);
    FileDownloadList.getImpl().remove(this, MessageSnapshotTaker.catchPause(this));

    return true;
  }
  /**
   * Reuse this task withhold request params: path、url、header、isForceReDownloader、etc.
   *
   * @return Successful reuse or not.
   */
  public boolean reuse() {
    if (isRunning()) {
      FileDownloadLog.w(
          this,
          "This task is running %d, if you want start the same task,"
              + " please create a new one by FileDownloader.create",
          getId());
      return false;
    }

    this.using = false;
    this.etag = null;
    this.resuming = false;
    this.retryingTimes = 0;
    this.isReusedOldFile = false;
    this.ex = null;
    resetSpeed();
    clearMarkAdded2List();

    setStatus(FileDownloadStatus.INVALID_STATUS);
    this.soFarBytes = 0;
    this.totalBytes = 0;
    messenger.reAppointment(this);

    return true;
  }
  /**
   * start download
   *
   * <p>用于启动一个单独任务
   *
   * @return Download id
   */
  public int start() {
    if (FileDownloadMonitor.isValid()) {
      FileDownloadMonitor.getMonitor().onRequestStart(this);
    }

    if (FileDownloadLog.NEED_LOG) {
      FileDownloadLog.v(
          this,
          "call start " + "url[%s], setPath[%s] listener[%s], tag[%s]",
          url,
          path,
          listener,
          tag);
    }

    boolean ready = true;

    try {
      _adjust();
      _checkFile(path);
    } catch (Throwable e) {
      ready = false;

      setStatus(FileDownloadStatus.error);
      setEx(e);
      FileDownloadList.getImpl().add(this);
      FileDownloadList.getImpl().removeByError(this);
    }

    if (ready) {
      FileDownloadEventPool.getImpl().send2Service(new DownloadTaskEvent(this).requestStart());
    }

    return getDownloadId();
  }
  /**
   * Pause task
   *
   * <p>停止任务, 对于线程而言会直接关闭,清理所有相关数据,不会hold住任何东西
   *
   * <p>如果重新启动,默认会断点续传,所以为pause
   */
  public boolean pause() {
    setStatus(FileDownloadStatus.paused);

    final boolean result = _pauseExecute();

    // For make sure already added event listener for receive paused event
    FileDownloadList.getImpl().add(this);
    if (result) {
      FileDownloadList.getImpl().removeByPaused(this);
    } else {
      FileDownloadLog.w(this, "paused false %s", toString());
      // 一直依赖不在下载进程队列中
      // 只有可能是 串行 还没有执行到 or 并行还没来得及加入进的
      FileDownloadList.getImpl().removeByPaused(this);
    }
    return result;
  }
  private void update(final MessageSnapshot snapshot) {
    setStatus(snapshot.getStatus());
    this.isLargeFile = snapshot.isLargeFile();

    switch (snapshot.getStatus()) {
      case FileDownloadStatus.pending:
        this.soFarBytes = snapshot.getLargeSofarBytes();
        this.totalBytes = snapshot.getLargeTotalBytes();

        // notify
        getMessenger().notifyPending(snapshot);
        break;
      case FileDownloadStatus.started:
        // notify
        getMessenger().notifyStarted(snapshot);
        break;
      case FileDownloadStatus.connected:
        this.totalBytes = snapshot.getLargeTotalBytes();
        this.resuming = snapshot.isResuming();
        this.etag = snapshot.getEtag();

        markStartDownload();

        // notify
        getMessenger().notifyConnected(snapshot);
        break;
      case FileDownloadStatus.progress:
        this.soFarBytes = snapshot.getLargeSofarBytes();
        calcSpeed(snapshot.getLargeSofarBytes());

        // notify
        getMessenger().notifyProgress(snapshot);
        break;
        //            case FileDownloadStatus.blockComplete:
        /** Handled by {@link FileDownloadList#removeByCompleted(BaseDownloadTask)} */
        //                break;
      case FileDownloadStatus.retry:
        this.soFarBytes = snapshot.getLargeSofarBytes();
        this.ex = snapshot.getThrowable();
        _setRetryingTimes(snapshot.getRetryingTimes());

        resetSpeed();
        // notify
        getMessenger().notifyRetry(snapshot);
        break;
      case FileDownloadStatus.error:
        this.ex = snapshot.getThrowable();
        this.soFarBytes = snapshot.getLargeSofarBytes();

        calcAverageSpeed(this.soFarBytes);
        // to FileDownloadList
        FileDownloadList.getImpl().remove(this, snapshot);

        break;
      case FileDownloadStatus.paused:
        /** Handled by {@link #pause()} */
        break;
      case FileDownloadStatus.completed:
        this.isReusedOldFile = snapshot.isReusedDownloadedFile();
        if (snapshot.isReusedDownloadedFile()) {
          this.etag = snapshot.getEtag();
        }
        // only carry total data back
        this.soFarBytes = snapshot.getLargeTotalBytes();
        this.totalBytes = snapshot.getLargeTotalBytes();

        calcAverageSpeed(this.soFarBytes);
        // to FileDownloadList
        FileDownloadList.getImpl().remove(this, snapshot);

        break;
      case FileDownloadStatus.warn:
        resetSpeed();
        final int count = FileDownloadList.getImpl().count(getId());
        if (count <= 1) {
          // 1. this progress kill by sys and relive,
          // for add at least one listener
          // or 2. pre downloading task has already completed/error/paused
          // request status
          final int currentStatus = _getStatusFromServer(downloadId);
          FileDownloadLog.w(
              this,
              "warn, but no listener to receive progress, " + "switch to pending %d %d",
              getId(),
              currentStatus);

          //noinspection StatementWithEmptyBody
          if (FileDownloadStatus.isIng(currentStatus)) {
            // ing, has callbacks
            // keep and wait callback

            setStatus(FileDownloadStatus.pending);
            this.totalBytes = snapshot.getLargeTotalBytes();
            this.soFarBytes = snapshot.getLargeSofarBytes();

            markStartDownload();

            ((MessageSnapshot.IWarnMessageSnapshot) snapshot).turnToPending();
            getMessenger().notifyPending(snapshot);
            break;
          } else {
            // already over and no callback
          }
        }

        // to FileDownloadList
        FileDownloadList.getImpl().remove(this, snapshot);
        break;
    }
  }
 MessageSnapshot catchException(Throwable ex) {
   setStatus(FileDownloadStatus.error);
   this.ex = ex;
   return MessageSnapshotTaker.catchException(this);
 }
  /** @param transfer In order to optimize some of the data in some cases is not back */
  void update(final FileDownloadTransferModel transfer) {
    switch (transfer.getStatus()) {
      case FileDownloadStatus.pending:
        if (getStatus() == FileDownloadStatus.pending) {
          FileDownloadLog.w(this, "already pending %d", getDownloadId());
          break;
        }
        this.setStatus(transfer.getStatus());
        this.setSoFarBytes(transfer.getSoFarBytes());
        this.setTotalBytes(transfer.getTotalBytes());

        // notify
        getDriver().notifyPending();
        break;
      case FileDownloadStatus.connected:
        if (getStatus() == FileDownloadStatus.connected) {
          FileDownloadLog.w(this, "already connected %d", getDownloadId());
          break;
        }

        setStatus(transfer.getStatus());
        setTotalBytes(transfer.getTotalBytes());
        setSoFarBytes(transfer.getSoFarBytes());
        this.isContinue = transfer.isContinue();
        this.etag = transfer.getEtag();

        // notify
        getDriver().notifyConnected();
        break;
      case FileDownloadStatus.progress:
        if (getStatus() == FileDownloadStatus.progress
            && transfer.getSoFarBytes() == getLargeFileSoFarBytes()) {
          FileDownloadLog.w(this, "%d unused values! by process callback", getDownloadId());
          break;
        }

        setStatus(transfer.getStatus());
        setSoFarBytes(transfer.getSoFarBytes());

        // notify
        getDriver().notifyProgress();
        break;
      case FileDownloadStatus.blockComplete:
        /** Handled by {@link FileDownloadList#removeByCompleted(BaseDownloadTask)} */
        break;
      case FileDownloadStatus.retry:
        if (getStatus() == FileDownloadStatus.retry
            && getRetryingTimes() == transfer.getRetryingTimes()) {
          FileDownloadLog.w(
              this,
              "%d already retry! %d %d %s",
              getDownloadId(),
              getRetryingTimes(),
              getAutoRetryTimes(),
              transfer.getThrowable());
          break;
        }

        setStatus(transfer.getStatus());
        setSoFarBytes(transfer.getSoFarBytes());
        setEx(transfer.getThrowable());
        _setRetryingTimes(transfer.getRetryingTimes());

        // notify
        getDriver().notifyRetry();
        break;
      case FileDownloadStatus.error:
        if (getStatus() == FileDownloadStatus.error) {
          FileDownloadLog.w(
              this,
              "%d already err(%s) , callback by other status same transfer",
              getDownloadId(),
              getEx());
          break;
        }

        setStatus(transfer.getStatus());
        setEx(transfer.getThrowable());
        setSoFarBytes(transfer.getSoFarBytes());

        // to FileDownloadList
        FileDownloadList.getImpl().removeByError(this);

        break;
      case FileDownloadStatus.paused:
        /** Handled by {@link #pause()} */
        break;
      case FileDownloadStatus.completed:
        if (getStatus() == FileDownloadStatus.completed) {
          FileDownloadLog.w(
              this,
              "%d already completed , callback by process with same transfer",
              getDownloadId());
          break;
        }

        this.isReusedOldFile = transfer.isUseOldFile();
        setStatus(transfer.getStatus());
        // only carry total data back
        setSoFarBytes(transfer.getTotalBytes());
        setTotalBytes(transfer.getTotalBytes());

        // to FileDownloadList
        FileDownloadList.getImpl().removeByCompleted(this);

        break;
      case FileDownloadStatus.warn:
        if (getStatus() == FileDownloadStatus.warn) {
          FileDownloadLog.w(
              this, "%d already warn , callback by process with same transfer", getDownloadId());
          break;
        }

        final int count = FileDownloadList.getImpl().count(getDownloadId());
        if (count <= 1) {
          // 1. this progress kill by sys and relive,
          // for add at least one listener
          // or 2. pre downloading task has already completed/error/paused
          // request status
          final int currentStatus = _getStatusFromServer(downloadId);
          FileDownloadLog.w(
              this,
              "warn, but no listener to receive progress, " + "switch to pending %d %d",
              getDownloadId(),
              currentStatus);

          if (FileDownloadStatus.isIng(currentStatus)) {
            // ing, has callbacks
            // keep and wait callback

            setStatus(FileDownloadStatus.pending);
            getDriver().notifyPending();
            break;
          } else {
            // already over and no callback
          }
        }

        setStatus(transfer.getStatus());

        // to FileDownloadList
        FileDownloadList.getImpl().removeByWarn(this);
        break;
    }
  }