private void deleteFile(FileLockCompanion fileLockCompanion) {
   if (fileLockCompanion.getFile() != null && deleteFile.get()) {
     releaseLock(fileLockCompanion);
     activeDownloadFiles.remove(fileLockCompanion.getFile());
     fileLockCompanion.getFile().delete();
   }
 }
 private void releaseLock(FileLockCompanion fileLockCompanion) {
   try {
     if (fileLockCompanion.getLock() != null) {
       try {
         fileLockCompanion.getLock().channel().close();
         fileLockCompanion.getLock().release();
       } finally {
         if (fileLockCompanion.getLockedPathFile() != null) {
           new File(fileLockCompanion.getLockedPathFile()).delete();
         }
       }
     }
   } catch (IOException e) {
     // Ignore.
   }
 }
  /**
   * Create a {@link FileLockCompanion} containing a reference to a temporary {@link File} used when
   * downloading a remote file. If a local and incomplete version of a file is available, use that
   * file and resume bytes downloading. To prevent multiple process trying to resume the same file,
   * a {@link FileLock} companion to the tmeporary file is created and used to prevent concurrency
   * issue.
   *
   * @param path The downloaded path
   * @param allowResumable Allow resumable download, or not.
   * @return
   */
  private FileLockCompanion createOrGetTmpFile(String path, boolean allowResumable) {
    if (!disableResumeSupport && allowResumable) {
      File f = new File(path);
      File parentFile = f.getParentFile();
      if (parentFile.isDirectory()) {
        for (File tmpFile :
            parentFile.listFiles(
                new FilenameFilter() {
                  public boolean accept(File dir, String name) {
                    if (name.indexOf(".") > 0 && name.lastIndexOf(".") == name.indexOf(".ahc")) {
                      return true;
                    }
                    return false;
                  }
                })) {

          if (tmpFile.length() > 0) {
            String realPath = tmpFile.getPath().substring(0, tmpFile.getPath().lastIndexOf("."));

            FileLockCompanion fileLockCompanion = null;
            if (realPath.equals(path)) {
              File newFile = tmpFile;
              synchronized (activeDownloadFiles) {
                fileLockCompanion = lockFile(tmpFile);
                logger.debug(String.format("Found an incomplete download for file %s.", path));

                if (fileLockCompanion.getLock() == null) {
                  /** Lock failed so we need to regenerate a new tmp file. */
                  newFile = getTmpFile(path);
                  fileLockCompanion = lockFile(newFile);
                }
                return fileLockCompanion;
              }
            }
          }
        }
      }
    }
    return new FileLockCompanion(getTmpFile(path), null);
  }
    public void run() {
      download.setState(Transfer.State.ACTIVE);
      final String uri = validateUri(path);
      final TransferResource transferResource =
          new DefaultTransferResource(repository.getUrl(), path, file, download.getTrace());
      final boolean ignoreChecksum = RepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals(checksumPolicy);
      CompletionHandler completionHandler = null;

      final FileLockCompanion fileLockCompanion =
          (file != null)
              ? createOrGetTmpFile(file.getPath(), allowResumable)
              : new FileLockCompanion(null, null);

      try {
        long length = 0;
        if (fileLockCompanion.getFile() != null) {
          fileProcessor.mkdirs(fileLockCompanion.getFile().getParentFile());
        }

        // Position the file to the end in case we are resuming an aborded download.
        final RandomAccessFile resumableFile =
            fileLockCompanion.getFile() == null
                ? null
                : new RandomAccessFile(fileLockCompanion.getFile(), "rw");
        if (resumableFile != null) {
          length = resumableFile.length();
        }

        FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap();
        if (!useCache) {
          headers.add("Pragma", "no-cache");
        }
        headers.add("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");
        headers.replaceAll(AsyncRepositoryConnector.this.headers);

        Request request = null;
        final AtomicInteger maxRequestTry = new AtomicInteger();
        AsyncHttpClient client = httpClient;
        final AtomicBoolean closeOnComplete = new AtomicBoolean(false);

        /**
         * If length > 0, it means we are resuming a interrupted download. If that's the case, we
         * can't re-use the current httpClient because compression is enabled, and supporting
         * compression per request is not supported in ahc and may never has it could have a
         * performance impact.
         */
        if (length > 0) {
          AsyncHttpClientConfig config = createConfig(session, repository, false);
          client = new AsyncHttpClient(new NettyAsyncHttpProvider(config));
          request = client.prepareGet(uri).setRangeOffset(length).setHeaders(headers).build();
          closeOnComplete.set(true);
        } else {
          request = httpClient.prepareGet(uri).setHeaders(headers).build();
        }

        final Request activeRequest = request;
        final AsyncHttpClient activeHttpClient = client;
        completionHandler =
            new CompletionHandler(transferResource, httpClient, logger, RequestType.GET) {
              private final AtomicBoolean seekEndOnFile = new AtomicBoolean(false);

              private final AtomicBoolean handleTmpFile = new AtomicBoolean(true);

              private final AtomicBoolean localException = new AtomicBoolean(false);

              /** {@inheritDoc} */
              @Override
              public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception {

                FluentCaseInsensitiveStringsMap h = headers.getHeaders();
                String rangeByteValue = h.getFirstValue("Content-Range");
                // Make sure the server acceptance of the range requests headers
                if (rangeByteValue != null && rangeByteValue.compareToIgnoreCase("none") != 0) {
                  seekEndOnFile.set(true);
                }
                return super.onHeadersReceived(headers);
              }

              @Override
              public void onThrowable(Throwable t) {
                try {
                  logger.debug("onThrowable", t);
                  /**
                   * If an IOException occurs, let's try to resume the request based on how much
                   * bytes has been so far downloaded. Fail after IOException.
                   */
                  if (!disableResumeSupport
                      && !localException.get()
                      && maxRequestTry.get() < maxIOExceptionRetry
                      && IOException.class.isAssignableFrom(t.getClass())) {
                    logger.debug("Trying to recover from an IOException " + activeRequest);
                    maxRequestTry.incrementAndGet();
                    Request newRequest =
                        new RequestBuilder(activeRequest)
                            .setRangeOffset(resumableFile.length())
                            .build();
                    activeHttpClient.executeRequest(newRequest, this);
                    deleteFile.set(false);
                    return;
                  }
                  localException.set(false);

                  if (closeOnComplete.get()) {
                    activeHttpClient.close();
                  }

                  super.onThrowable(t);
                  if (Exception.class.isAssignableFrom(t.getClass())) {
                    exception = Exception.class.cast(t);
                  } else {
                    exception = new Exception(t);
                  }
                  fireTransferFailed();
                } catch (Throwable ex) {
                  logger.debug("Unexpected exception", ex);
                } finally {
                  if (resumableFile != null) {
                    try {
                      resumableFile.close();
                    } catch (IOException ex) {
                    }
                  }
                  deleteFile(fileLockCompanion);

                  latch.countDown();
                  removeListeners();
                }
              }

              private void removeListeners() {
                removeTransferListener(listener);
              }

              public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception {
                if (status() != null
                    && (status().getStatusCode() == 200 || status().getStatusCode() == 206)) {
                  byte[] bytes = content.getBodyPartBytes();
                  try {
                    // If the content-range header was present, save the bytes at the end of the
                    // file
                    // as we are resuming an existing download.
                    if (seekEndOnFile.get()) {
                      resumableFile.seek(fileLockCompanion.getFile().length());

                      // No need to seek again.
                      seekEndOnFile.set(false);
                    }
                    resumableFile.write(bytes);
                  } catch (IOException ex) {
                    logger.debug("onBodyPartReceived", ex);
                    exception = ex;
                    localException.set(true);
                    throw ex;
                  }
                }
                return super.onBodyPartReceived(content);
              }

              @Override
              public Response onCompleted(Response r) throws Exception {
                try {
                  deleteFile.set(true);
                  try {
                    resumableFile.close();
                  } catch (IOException ex) {
                  }

                  final Response response = super.onCompleted(r);

                  handleResponseCode(uri, response.getStatusCode(), response.getStatusText());

                  if (!ignoreChecksum) {
                    activeHttpClient
                        .getConfig()
                        .executorService()
                        .execute(
                            new Runnable() {
                              public void run() {
                                try {
                                  try {
                                    Map<String, Object> checksums =
                                        ChecksumUtils.calc(
                                            fileLockCompanion.getFile(), checksumAlgos.keySet());
                                    if (!verifyChecksum(
                                            file, uri, (String) checksums.get("SHA-1"), ".sha1")
                                        && !verifyChecksum(
                                            file, uri, (String) checksums.get("MD5"), ".md5")) {
                                      throw new ChecksumFailureException(
                                          "Checksum validation failed"
                                              + ", no checksums available from the repository");
                                    }
                                  } catch (ChecksumFailureException e) {
                                    if (RepositoryPolicy.CHECKSUM_POLICY_FAIL.equals(
                                        checksumPolicy)) {
                                      throw e;
                                    }
                                    if (listener != null) {
                                      listener.transferCorrupted(
                                          newEvent(
                                              transferResource,
                                              e,
                                              RequestType.GET,
                                              EventType.CORRUPTED));
                                    }
                                  }
                                } catch (Exception ex) {
                                  exception = ex;
                                } finally {
                                  if (exception == null) {
                                    try {
                                      rename(fileLockCompanion.getFile(), file);
                                      releaseLock(fileLockCompanion);
                                    } catch (IOException e) {
                                      exception = e;
                                    }
                                  } else {
                                    deleteFile(fileLockCompanion);
                                  }

                                  latch.countDown();
                                  if (closeOnComplete.get()) {
                                    activeHttpClient.close();
                                  }
                                }
                              }
                            });
                  } else {

                    rename(fileLockCompanion.getFile(), file);
                    releaseLock(fileLockCompanion);
                    handleTmpFile.set(false);

                    // asyncHttpClient.close may takes time before all connections get closed.
                    // We unlatch first.
                    latch.countDown();
                    if (closeOnComplete.get()) {
                      activeHttpClient.close();
                    }
                  }
                  removeListeners();

                  return response;
                } catch (Exception ex) {
                  exception = ex;
                  localException.set(true);
                  throw ex;
                } finally {
                  try {
                    if (handleTmpFile.get() && fileLockCompanion.getFile() != null) {
                      if (exception != null) {
                        deleteFile(fileLockCompanion);
                      } else if (ignoreChecksum) {
                        rename(fileLockCompanion.getFile(), file);
                        releaseLock(fileLockCompanion);
                      }
                    }
                  } catch (IOException ex) {
                    exception = ex;
                  }
                }
              }
            };

        try {
          if (file == null) {
            if (!resourceExist(uri)) {
              throw new ResourceDoesNotExistException(
                  "Could not find " + uri + " in " + repository.getUrl());
            }
            latch.countDown();
          } else {
            if (listener != null) {
              completionHandler.addTransferListener(listener);
              listener.transferInitiated(
                  newEvent(transferResource, null, RequestType.GET, EventType.INITIATED));
            }

            activeHttpClient.executeRequest(request, completionHandler);
          }
        } catch (Exception ex) {
          try {
            if (resumableFile != null) {
              resumableFile.close();
            }
          } catch (IOException ex2) {
          }
          deleteFile(fileLockCompanion);
          exception = ex;
          latch.countDown();
        }
      } catch (Throwable t) {
        deleteFile(fileLockCompanion);
        try {
          if (Exception.class.isAssignableFrom(t.getClass())) {
            exception = Exception.class.cast(t);
          } else {
            exception = new Exception(t);
          }
          if (listener != null) {
            listener.transferFailed(
                newEvent(transferResource, exception, RequestType.GET, EventType.FAILED));
          }
        } finally {
          latch.countDown();
        }
      }
    }