/** * Abiquo community edition * cloud management application for hybrid clouds * Copyright (C) 2008-2010 - Abiquo Holdings S.L. * * This application is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC * LICENSE as published by the Free Software Foundation under * version 3 of the License * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * LESSER GENERAL PUBLIC LICENSE v.3 for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.abiquo.am.services.download; import static com.abiquo.am.services.filesystem.EnterpriseRepositoryFileSystem.releaseFile; import static com.abiquo.am.services.filesystem.EnterpriseRepositoryFileSystem.takeFile; import static com.abiquo.appliancemanager.exceptions.AMException.getErrorMessage; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import javax.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.abiquo.am.data.AMRedisDao; import com.abiquo.am.services.ErepoFactory; import com.abiquo.am.services.notify.AMNotifier; import com.abiquo.appliancemanager.config.AMConfiguration; import com.abiquo.appliancemanager.transport.TemplateStatusEnumType; import com.ning.http.client.AsyncHandler; import com.ning.http.client.HttpResponseBodyPart; import com.ning.http.client.HttpResponseHeaders; import com.ning.http.client.HttpResponseStatus; /** * A file on an OVF package being download. Used as session object on the HttpResponseHandler. */ public class DownloadingFile implements AsyncHandler<Boolean> { private final static Logger LOG = LoggerFactory.getLogger(DownloadingFile.class); /** Already read bytes. */ protected volatile long currentBytes; /** All the expected bytes on the file. Based on the Content-Length header. */ protected long expectedBytes; /** Target path where the file will be download (on some enterprise repository). */ protected final String destinationPath; /** An open stream to the destinationPath. */ protected final BufferedOutputStream osDestFile; /** The package is being cancelled. */ protected volatile boolean isCancell = false; /** The file ends download. */ protected volatile boolean isDone = false; protected volatile boolean isError = false; private final static Integer B_2_MB = 1048576; private long lastNotifyMs = 0; /** Source URL of the file being download. */ public final String fileUrl; public final String erepoId; public final String ovfId; private final AMNotifier notifier; public DownloadingFile(final String fileUrl, final String destinationPath, final String enterpriseid, final String ovfId, final AMNotifier notifier) { this.fileUrl = fileUrl; this.erepoId = enterpriseid; this.ovfId = ovfId; this.destinationPath = destinationPath; this.osDestFile = takeFile(destinationPath); this.notifier = notifier; } @Override public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { LOG.debug("GET status {} received {}", status.getStatusCode(), fileUrl); if (status.getStatusCode() / 200 != 1) { onError(getErrorMessage(status.getStatusCode(), fileUrl)); return STATE.ABORT; } else { return STATE.CONTINUE; } } @Override public STATE onHeadersReceived(final HttpResponseHeaders h) throws Exception { try { expectedBytes = Long.parseLong(h.getHeaders().getFirstValue(HttpHeaders.CONTENT_LENGTH)); LOG.debug("File {} will download {} Mb", destinationPath, expectedBytes / B_2_MB); } catch (Exception e) { onError("Content-Length header not present"); } return STATE.CONTINUE; } @Override public STATE onBodyPartReceived(final HttpResponseBodyPart bodyPart) throws Exception { if (isCancell) { LOG.warn("Download aborted {}", fileUrl); return STATE.ABORT; } try { currentBytes += bodyPart.writeTo(osDestFile); updateProgress(); return STATE.CONTINUE; } catch (IOException e) { e.printStackTrace(); onError(String.format("Can't flush content to %s\n%s", destinationPath, e.getMessage())); return STATE.ABORT; } } private void updateProgress() { final long now = System.currentTimeMillis(); if (now - lastNotifyMs > AMConfiguration.DOWNLOADING_PUBLISH_INTERVAL) { final Integer progress = (int) (currentBytes * 100 / expectedBytes); LOG.trace("{} {}", ovfId, progress); AMRedisDao dao = AMRedisDao.getDao(); try { dao.setDownloadProgress(erepoId, ovfId, progress); } finally { AMRedisDao.returnDao(dao); } lastNotifyMs = now; } } /** * Will be invoked once the response has been fully read or a ResponseComplete exception has * been thrown. */ @Override public Boolean onCompleted() throws Exception { if (!isError && !isCancell) { onDownload(); } return Boolean.TRUE; } @Override public void onThrowable(final Throwable t) { // annoying AHC 1.0.0 bug ... internal NPE String error = t.getLocalizedMessage(); error = error != null ? error : "Internal AHC error " + t.getClass().getCanonicalName(); onError(error); t.printStackTrace(); } /** * When the file ends the download. Remove the file mark and advice the OVF package some file * ends. */ public void onDownload() { isDone = true; try { osDestFile.flush(); osDestFile.close(); } catch (IOException e) { onError("Can not close file " + destinationPath); return; } releaseFile(destinationPath); // AMRedisDao dao = AMRedisDao.getDao(); // dao.setDownloadProgress(erepoId, ovfId, 100); // // dao.setState(ovfId, OVFStatusEnumType.DOWNLOAD); // AMRedisDao.returnDao(dao); notifier.setTemplateStatus(erepoId, ovfId, TemplateStatusEnumType.DOWNLOAD); } /** * When the file (and its package) was cancelled. Remove the file. */ public void onCancel(final boolean deleteFolder) { isCancell = true; try { osDestFile.flush(); osDestFile.close(); } catch (IOException e) { onError("Can not close file " + destinationPath); return; } releaseFile(destinationPath); File destiantion = new File(destinationPath); destiantion.delete(); if (deleteFolder) { ErepoFactory.getRepo(erepoId).deleteTemplate(ovfId); notifier.setTemplateStatus(erepoId, ovfId, TemplateStatusEnumType.NOT_DOWNLOAD); } else { AMRedisDao dao = AMRedisDao.getDao(); try { // dao.setDownloadProgress(erepoId, ovfId, 0); dao.setState(erepoId, ovfId, TemplateStatusEnumType.NOT_DOWNLOAD); } finally { AMRedisDao.returnDao(dao); } } } private void onError(final String msg) { isError = true; onCancel(false); // NO delete folder (to remain error cause ). notifier.setTemplateStatusError(erepoId, ovfId, msg); } public boolean done() { return isCancell || isDone || isError; } }