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(); } } }