@Override
  public long download(boolean resume, DownloadCompleteCallback callback) {
    switch (status) {
      case ABORTED:
      case UNRECOVERABLE_ERROR:
      case DOWNLOAD_FINISHED:
        return 0;
      default:
    }
    int bytes = 0;
    File file = new File(toFile);
    try {

      long localFileSize = 0;
      if (file.exists() && resume) {
        localFileSize = file.length();
        s_logger.info("Resuming download to file (current size)=" + localFileSize);
      }

      Date start = new Date();

      int responseCode = 0;

      if (localFileSize > 0) {
        // require partial content support for resume
        request.addRequestHeader("Range", "bytes=" + localFileSize + "-");
        if (client.executeMethod(request) != HttpStatus.SC_PARTIAL_CONTENT) {
          errorString = "HTTP Server does not support partial get";
          status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
          return 0;
        }
      } else if ((responseCode = client.executeMethod(request)) != HttpStatus.SC_OK) {
        status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
        errorString = " HTTP Server returned " + responseCode + " (expected 200 OK) ";
        return 0; // FIXME: retry?
      }

      Header contentLengthHeader = request.getResponseHeader("Content-Length");
      boolean chunked = false;
      long remoteSize2 = 0;
      if (contentLengthHeader == null) {
        Header chunkedHeader = request.getResponseHeader("Transfer-Encoding");
        if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
          status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
          errorString = " Failed to receive length of download ";
          return 0; // FIXME: what status do we put here? Do we retry?
        } else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
          chunked = true;
        }
      } else {
        remoteSize2 = Long.parseLong(contentLengthHeader.getValue());
      }

      if (remoteSize == 0) {
        remoteSize = remoteSize2;
      }

      if (remoteSize > MAX_TEMPLATE_SIZE_IN_BYTES) {
        s_logger.info(
            "Remote size is too large: " + remoteSize + " , max=" + MAX_TEMPLATE_SIZE_IN_BYTES);
        status = Status.UNRECOVERABLE_ERROR;
        errorString = "Download file size is too large";
        return 0;
      }

      if (remoteSize == 0) {
        remoteSize = MAX_TEMPLATE_SIZE_IN_BYTES;
      }

      InputStream in =
          !chunked
              ? new BufferedInputStream(request.getResponseBodyAsStream())
              : new ChunkedInputStream(request.getResponseBodyAsStream());

      RandomAccessFile out = new RandomAccessFile(file, "rwd");
      out.seek(localFileSize);

      s_logger.info(
          "Starting download from "
              + getDownloadUrl()
              + " to "
              + toFile
              + " remoteSize="
              + remoteSize
              + " , max size="
              + MAX_TEMPLATE_SIZE_IN_BYTES);

      byte[] block = new byte[CHUNK_SIZE];
      long offset = 0;
      boolean done = false;
      status = TemplateDownloader.Status.IN_PROGRESS;
      while (!done && status != Status.ABORTED && offset <= remoteSize) {
        if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
          out.write(block, 0, bytes);
          offset += bytes;
          out.seek(offset);
          totalBytes += bytes;
        } else {
          done = true;
        }
      }
      Date finish = new Date();
      String downloaded = "(incomplete download)";
      if (totalBytes >= remoteSize) {
        status = TemplateDownloader.Status.DOWNLOAD_FINISHED;
        downloaded = "(download complete remote=" + remoteSize + "bytes)";
      }
      errorString = "Downloaded " + totalBytes + " bytes " + downloaded;
      downloadTime += finish.getTime() - start.getTime();
      out.close();

      return totalBytes;
    } catch (HttpException hte) {
      status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
      errorString = hte.getMessage();
    } catch (IOException ioe) {
      status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; // probably a file write error?
      errorString = ioe.getMessage();
    } finally {
      if (status == Status.UNRECOVERABLE_ERROR && file.exists() && !file.isDirectory()) {
        file.delete();
      }
      request.releaseConnection();
      if (callback != null) {
        callback.downloadComplete(status);
      }
    }
    return 0;
  }
  @Override
  public long download(boolean resume, DownloadCompleteCallback callback) {
    if (_status == Status.ABORTED
        || _status == Status.UNRECOVERABLE_ERROR
        || _status == Status.DOWNLOAD_FINISHED) {
      return 0;
    }

    _start = System.currentTimeMillis();
    _resume = resume;

    File src;
    try {
      src = new File(new URI(_downloadUrl));
    } catch (URISyntaxException e1) {
      s_logger.warn("Invalid URI " + _downloadUrl);
      _status = Status.UNRECOVERABLE_ERROR;
      return 0;
    }
    File dst = new File(_toFile);

    FileChannel fic = null;
    FileChannel foc = null;
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
      if (_storage != null) {
        dst.createNewFile();
        _storage.setWorldReadableAndWriteable(dst);
      }

      ByteBuffer buffer = ByteBuffer.allocate(1024 * 512);

      try {
        fis = new FileInputStream(src);
      } catch (FileNotFoundException e) {
        s_logger.warn("Unable to find " + _downloadUrl);
        _errorString = "Unable to find " + _downloadUrl;
        return -1;
      }
      fic = fis.getChannel();
      try {
        fos = new FileOutputStream(dst);
      } catch (FileNotFoundException e) {
        s_logger.warn("Unable to find " + _toFile);
        return -1;
      }
      foc = fos.getChannel();

      _remoteSize = src.length();
      _totalBytes = 0;
      _status = TemplateDownloader.Status.IN_PROGRESS;

      try {
        while (_status != Status.ABORTED && fic.read(buffer) != -1) {
          buffer.flip();
          int count = foc.write(buffer);
          _totalBytes += count;
          buffer.clear();
        }
      } catch (IOException e) {
        s_logger.warn("Unable to download", e);
      }

      String downloaded = "(incomplete download)";
      if (_totalBytes == _remoteSize) {
        _status = TemplateDownloader.Status.DOWNLOAD_FINISHED;
        downloaded = "(download complete)";
      }

      _errorString = "Downloaded " + _remoteSize + " bytes " + downloaded;
      _downloadTime += System.currentTimeMillis() - _start;
      return _totalBytes;
    } catch (Exception e) {
      _status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
      _errorString = e.getMessage();
      return 0;
    } finally {
      if (fic != null) {
        try {
          fic.close();
        } catch (IOException e) {
          s_logger.info("[ignore] error while closing file input channel.");
        }
      }

      if (foc != null) {
        try {
          foc.close();
        } catch (IOException e) {
          s_logger.info("[ignore] error while closing file output channel.");
        }
      }

      if (fis != null) {
        try {
          fis.close();
        } catch (IOException e) {
          s_logger.info("[ignore] error while closing file input stream.");
        }
      }

      if (fos != null) {
        try {
          fos.close();
        } catch (IOException e) {
          s_logger.info("[ignore] error while closing file output stream.");
        }
      }

      if (_status == Status.UNRECOVERABLE_ERROR && dst.exists()) {
        dst.delete();
      }
      if (callback != null) {
        callback.downloadComplete(_status);
      }
    }
  }