@Override
  public void download(final com.illumina.basespace.File file, java.io.File target) {
    FileOutputStream fos = null;
    InputStream in = null;
    boolean canceled = false;
    try {
      final int CHUNK_SIZE = 4096;
      if (target.isDirectory()) {
        if (!target.exists() && !target.mkdirs()) {
          throw new IllegalArgumentException("Unable to create local folder " + target.toString());
        }
        target = new java.io.File(target, file.getName());
      }
      in = getFileInputStream(file);
      fos = new FileOutputStream(target);
      long progress = 0;
      byte[] outputByte = new byte[CHUNK_SIZE];
      int bytesRead = 0;

      readTheFile:
      while ((bytesRead = in.read(outputByte, 0, CHUNK_SIZE)) != -1) {
        fos.write(outputByte, 0, bytesRead);
        progress += bytesRead;
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), progress, file.getSize());
        fireProgressEvent(evt);
        if (evt.isCanceled()) {
          canceled = true;
          break readTheFile;
        }
      }
      fos.close();
      in.close();
      fos = null;
      in = null;
    } catch (BaseSpaceException bs) {
      throw bs;
    } catch (Throwable t) {
      throw new RuntimeException("Error during file download", t);
    } finally {
      try {
        if (in != null) in.close();
      } catch (Throwable t) {
      }
      try {
        if (fos != null) fos.close();
      } catch (Throwable t) {
      }
      if (canceled) {
        if (target != null) target.delete();
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), 0, file.getSize());
        fireCanceledEvent(evt);
      } else {
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), file.getSize(), file.getSize());
        fireCompleteEvent(evt);
      }
    }
  }
  /**
   * download
   *
   * <p>Expose a single chunk of download to the client. Thread-safe API call. Multiple threads may
   * use this call to do parallel fetches of a single file.
   *
   * <p>This is a range download from file, starting at fileStart for len bytes. It will write data
   * into target starting at targetStart. If file is a directory, this will use
   * target/<file.getName()>-start-len.dat
   */
  @Override
  public void download(
      final com.illumina.basespace.File file,
      long fileStart,
      long len,
      java.io.File target,
      long targetStart) {
    FileChannel fc = null;
    RandomAccessFile ras = null;
    InputStream in = null;
    boolean canceled = false;

    final int CHUNK_SIZE = 8192; // for part downloads, reduce the number of calls by 1/2.
    long progress = 0;

    try {
      if ((fileStart + len) > file.getSize()) {
        throw new Exception(
            "Invalid download range start("
                + fileStart
                + ") + len ("
                + len
                + ") > file size ("
                + file.getSize()
                + ")");
      }
      if (target.isDirectory()) {
        if (!target.exists() && !target.mkdirs()) {
          throw new IllegalArgumentException("Unable to create local folder " + target.toString());
        }
        target = new java.io.File(target, new String(file.getName()));
      }

      in =
          getFileInputStream(
              file, fileStart, fileStart + len - 1); // These are *positions*; end at len minus 1

      // Note: It sounds simple to use a FileChannel to sparsely write a file.  It isn't.
      // The code is sensitive to the right combination of usage.  I use a RandomAccessStream
      // and aquire a FileChannel from it.  It did not work if you just use the RAS.  It did not
      // work
      // if you try to open the FC w/o the RAS.  It did not work if you use a RAS:FC but allocate
      // a ByteBuffer once, call bb.clear, bb.put, then fc.write(bb).  This caused the FC to write
      // garbage for CHUNK_SIZE-bb.length.
      // Thus we now always use the ByteBuffer.wrap and the FC from a RAS.

      ras = new RandomAccessFile(target, "rw");
      fc = ras.getChannel(); // Open for WRITE default.
      fc.position(targetStart); // Place the position at our place in the file
      fc.force(true);
      progress = 0;
      byte[] outputByte = new byte[CHUNK_SIZE];
      int bytesRead = 0;
      readTheFile:
      while ((bytesRead = in.read(outputByte, 0, CHUNK_SIZE)) != -1) {
        fc.write(ByteBuffer.wrap(outputByte, 0, bytesRead));
        progress += bytesRead;
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), progress, len);
        fireProgressEvent(evt);
        if (evt.isCanceled()) {
          canceled = true;
          break readTheFile;
        }
      }
      fc.close();
      ras.close();
      in.close();
      fc = null;
      ras = null;
      in = null;
    } catch (BaseSpaceException bs) {
      throw bs;
    } catch (Throwable t) {
      throw new RuntimeException("Error during file download", t);
    } finally {
      try {
        if (fc != null) fc.close();
      } catch (Throwable t) {
      }
      try {
        if (in != null) in.close();
      } catch (Throwable t) {
      }
      try {
        if (ras != null) ras.close();
      } catch (Throwable t) {
      }
      if (canceled) {
        if (target != null) target.delete();
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), 0, len);
        fireCanceledEvent(evt);
      } else {
        // Could be called even if we get an exception, must pass progress, not length
        DownloadEvent evt = new DownloadEvent(this, file.getHref(), progress, len);
        fireCompleteEvent(evt);
      }
    }
  }