/* This file is part of RouteConverter. RouteConverter is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. RouteConverter 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 General Public License for more details. You should have received a copy of the GNU General Public License along with RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.download.performer; import slash.navigation.download.Action; import slash.navigation.download.Checksum; import slash.navigation.download.Download; import slash.navigation.download.actions.Copier; import slash.navigation.download.actions.Extractor; import slash.navigation.download.actions.Validator; import slash.navigation.download.executor.DownloadExecutor; import slash.navigation.download.executor.ModelUpdater; import slash.navigation.rest.Get; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; import static java.lang.String.format; import static java.util.logging.Logger.getLogger; import static slash.common.io.Directories.ensureDirectory; import static slash.common.io.Files.setLastModified; import static slash.navigation.download.State.*; /** * What the {@link DownloadExecutor} performs for {@link Action#Copy}, {@link Action#Extract}, {@link Action#Flatten}. */ public class GetPerformer implements ActionPerformer { private static final Logger log = getLogger(GetPerformer.class.getName()); private static final int SOCKET_TIMEOUT = 15 * 60 * 1000; private DownloadExecutor downloadExecutor; public void setDownloadExecutor(DownloadExecutor downloadExecutor) { this.downloadExecutor = downloadExecutor; } private Download getDownload() { return downloadExecutor.getDownload(); } private ModelUpdater getModelUpdater() { return downloadExecutor.getModelUpdater(); } private boolean canResume() { Checksum checksum = getDownload().getFile().getExpectedChecksum(); return getDownload().getTempFile().exists() && getDownload().getTempFile().length() > 0 && checksum != null && checksum.getContentLength() != null && checksum.getContentLength() > getDownload().getTempFile().length(); } private Result resume() throws IOException { downloadExecutor.updateState(Resuming); long fileSize = getDownload().getTempFile().length(); Long contentLength = getDownload().getFile().getExpectedChecksum() != null ? getDownload().getFile().getExpectedChecksum().getContentLength() : null; log.info(format("Resuming bytes %d-%d from %s", fileSize, contentLength, getDownload().getUrl())); Get get = new Get(getDownload().getUrl()); get.setRange(fileSize, contentLength); InputStream inputStream = get.executeAsStream(); log.info(format("Resume from %s returned with status code %s", getDownload().getUrl(), get.getStatusCode())); if (get.isPartialContent()) { getModelUpdater().expectingBytes(contentLength != null ? contentLength : get.getContentLength() != null ? get.getContentLength() : 0); new Copier(getModelUpdater()).copyAndClose(inputStream, new FileOutputStream(getDownload().getTempFile(), true), fileSize, contentLength); return new Result(true); } return new Result(false); } private Result download() throws IOException { downloadExecutor.updateState(Downloading); Long contentLength = getDownload().getFile().getExpectedChecksum() != null ? getDownload().getFile().getExpectedChecksum().getContentLength() : null; log.info(format("Downloading %d bytes from %s with ETag %s", contentLength, getDownload().getUrl(), getDownload().getETag())); Get get = new Get(getDownload().getUrl()); get.setSocketTimeout(SOCKET_TIMEOUT); if (new Validator(getDownload()).isExistsTargets() && getDownload().getETag() != null) get.setIfNoneMatch(getDownload().getETag()); InputStream inputStream = get.executeAsStream(); log.info(format("Download from %s returned with status code %s and content length %d", getDownload().getUrl(), get.getStatusCode(), get.getContentLength())); if (get.isSuccessful() && inputStream != null) { if(contentLength == null) contentLength = get.getContentLength(); if (contentLength != null) getModelUpdater().expectingBytes(contentLength); new Copier(getModelUpdater()).copyAndClose(inputStream, new FileOutputStream(getDownload().getTempFile()), 0, contentLength); getDownload().setETag(get.getETag()); return new Result(true, get.getLastModified()); } return new Result(get.isSuccessful(), get.isNotModified()); } public void run() throws IOException { Result result = new Result(false); if (canResume()) result = resume(); if (!result.success) result = download(); if (result.notModified) { downloadExecutor.notModified(); } else if (result.success) { if(!getDownload().getTempFile().exists()) downloadExecutor.downloadFailed(); if (postProcess(result.lastModified)) downloadExecutor.succeeded(); else downloadExecutor.postProcessFailed(); } else downloadExecutor.downloadFailed(); } private boolean postProcess(Long lastModified) throws IOException { downloadExecutor.updateState(Processing); bringToTarget(lastModified); if (!validate()) return false; if (getDownload().getTempFile().exists()) if (!getDownload().getTempFile().delete()) throw new IOException(format("Cannot delete temp file %s", getDownload().getTempFile())); log.fine(format("Postprocess from %s successful", getDownload().getUrl())); return true; } private void bringToTarget(Long lastModified) throws IOException { Action action = getDownload().getAction(); switch (action) { case Copy: copy(lastModified); break; case Flatten: flatten(lastModified); break; case Extract: extract(lastModified); break; default: throw new IllegalArgumentException("Unsupported Action " + action); } } private void copy(Long lastModified) throws IOException { File target = getDownload().getFile().getFile(); ensureDirectory(target.getParent()); new Copier(getModelUpdater()).copyAndClose(getDownload().getTempFile(), target); setLastModified(target, lastModified); } private void flatten(Long lastModified) throws IOException { File target = getDownload().getFile().getFile(); new Extractor(getModelUpdater()).flatten(getDownload().getTempFile(), target); setLastModified(getDownload().getTempFile(), lastModified); } private void extract(Long lastModified) throws IOException { File target = getDownload().getFile().getFile(); new Extractor(getModelUpdater()).extract(getDownload().getTempFile(), target); setLastModified(getDownload().getTempFile(), lastModified); } private boolean validate() throws IOException { downloadExecutor.updateState(Validating); Validator validator = new Validator(getDownload()); if (!validator.isExistsTargets()) { downloadExecutor.updateState(NoFileError); return false; } else if (!validator.isChecksumsValid()) { downloadExecutor.updateState(ChecksumError); return false; } // set expected to actual checksum for sending the expected checksum to the server later validator.expectedChecksumIsCurrentChecksum(); return true; } private static class Result { public final boolean success; public final boolean notModified; public final Long lastModified; public Result(boolean success) { this(success, null); } public Result(boolean success, Long lastModified) { this(success, false, lastModified); } private Result(boolean success, boolean notModified) { this(success, notModified, null); } private Result(boolean success, boolean notModified, Long lastModified) { this.success = success; this.notModified = notModified; this.lastModified = lastModified; } } }