/**
* Copyright (c) 2016 Codetrails GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andreas Sewe - initial API and implementation.
*/
package org.eclipse.recommenders.news.impl.poll;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.recommenders.internal.news.impl.poll.Proxies;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import com.google.common.annotations.VisibleForTesting;
public class DefaultDownloadService implements IDownloadService {
private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private static final long SOCKET_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private final Executor executor = Executor.newInstance();
private final Path downloadLocation;
public DefaultDownloadService() {
this(getStateLocation().resolve("downloads")); //$NON-NLS-1$
}
@VisibleForTesting
DefaultDownloadService(Path downloadLocation) {
this.downloadLocation = downloadLocation;
}
private static Path getStateLocation() {
Bundle bundle = FrameworkUtil.getBundle(DefaultDownloadService.class);
return Platform.getStateLocation(bundle).toFile().toPath();
}
@Override
public InputStream download(URI uri, @Nullable IProgressMonitor monitor) throws IOException {
SubMonitor progress = SubMonitor.convert(monitor, 1);
try {
String fileName = mangleUri(uri);
Path targetPath = downloadLocation.resolve(fileName);
doDownload(uri, fileName, targetPath, progress.newChild(1));
return Files.newInputStream(targetPath);
} finally {
if (monitor != null) {
monitor.done();
}
}
}
private void doDownload(URI uri, String fileName, Path targetFile, SubMonitor monitor) throws IOException {
SubMonitor progress = SubMonitor.convert(monitor, 4);
Path tempFile = null;
try (InputStream resourceStream = openWebResourceAsStream(uri, progress.newChild(1))) {
Files.createDirectories(downloadLocation);
tempFile = Files.createTempFile(downloadLocation, null, fileName);
progress.worked(1);
Files.copy(resourceStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
progress.worked(1);
Files.move(tempFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
progress.worked(1);
} catch (IOException e) {
progress.setWorkRemaining(0);
try {
Files.setLastModifiedTime(targetFile, FileTime.fromMillis(System.currentTimeMillis()));
} catch (IOException failedToSetLastModifiedTime) {
e.addSuppressed(failedToSetLastModifiedTime);
try {
Files.createFile(targetFile);
} catch (IOException failedToCreateFile) {
e.addSuppressed(failedToCreateFile);
}
}
throw e;
} finally {
if (tempFile != null) {
Files.deleteIfExists(tempFile);
}
}
}
private String mangleUri(URI uri) {
try {
return URLEncoder.encode(uri.toASCIIString(), StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(uri.toString(), e);
}
}
private InputStream openWebResourceAsStream(URI uri, SubMonitor monitor) throws IOException {
SubMonitor progress = SubMonitor.convert(monitor, 1);
try {
Request request = Request.Get(uri).viaProxy(Proxies.getProxyHost(uri))
.connectTimeout((int) CONNECTION_TIMEOUT).staleConnectionCheck(true)
.socketTimeout((int) SOCKET_TIMEOUT);
Response response = Proxies.proxyAuthentication(executor, uri).execute(request);
HttpResponse returnResponse = response.returnResponse();
StatusLine statusLine = returnResponse.getStatusLine();
if (statusLine == null) {
throw new IOException();
}
if (statusLine.getStatusCode() >= HttpStatus.SC_BAD_REQUEST) {
throw new IOException(statusLine.getReasonPhrase());
}
HttpEntity entity = returnResponse.getEntity();
if (entity == null || entity.getContentLength() == 0) {
throw new IOException("Empty representation"); //$NON-NLS-1$
}
return entity.getContent();
} finally {
progress.worked(1);
}
}
@Override
@Nullable
public InputStream read(URI uri) throws IOException {
String fileName = mangleUri(uri);
Path targetPath = downloadLocation.resolve(fileName);
try {
if (Files.size(targetPath) == 0L) {
throw new IOException("Empty representation"); //$NON-NLS-1$
}
return Files.newInputStream(targetPath);
} catch (NoSuchFileException e) {
return null;
} catch (IOException e) {
throw e;
}
}
@Override
@Nullable
public Date getLastAttemptDate(URI uri) throws IOException {
String fileName = mangleUri(uri);
Path targetPath = downloadLocation.resolve(fileName);
try {
return new Date(Files.getLastModifiedTime(targetPath).toMillis());
} catch (NoSuchFileException e) {
return null;
} catch (IOException e) {
throw e;
}
}
}