/**
* DataCleaner (community edition)
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.datacleaner.actions;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.swing.SwingWorker;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.metamodel.util.Action;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.job.tasks.Task;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.VFSUtils;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.util.http.InvalidHttpResponseException;
import org.datacleaner.util.http.WebServiceHttpClient;
import org.datacleaner.windows.FileTransferProgressWindow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ActionListener and SwingWorker implementation for handling download of a
* file. The progress will be displayed in a new window.
*/
public class DownloadFilesActionListener extends SwingWorker<FileObject[], Task> implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(DownloadFilesActionListener.class);
private final String[] _urls;
private final FileObject[] _files;
private final FileDownloadListener _listener;
private final FileTransferProgressWindow _downloadProgressWindow;
private final WebServiceHttpClient _httpClient;
private volatile boolean _cancelled = false;
public DownloadFilesActionListener(final String[] urls, final FileDownloadListener listener,
final WindowContext windowContext, final WebServiceHttpClient httpClient,
final UserPreferences userPreferences) {
this(urls, createTargetDirectory(userPreferences), createTargetFilenames(urls), listener, windowContext,
httpClient);
}
public DownloadFilesActionListener(final String[] urls, final String[] targetFilenames,
final FileDownloadListener listener, final WindowContext windowContext,
final WebServiceHttpClient httpClient, final UserPreferences userPreferences) {
this(urls, createTargetDirectory(userPreferences), targetFilenames, listener, windowContext, httpClient);
}
public DownloadFilesActionListener(final String[] urls, final FileObject targetDirectory,
final String[] targetFilenames, final FileDownloadListener listener, final WindowContext windowContext,
final WebServiceHttpClient httpClient) {
if (urls == null) {
throw new IllegalArgumentException("urls cannot be null");
}
_urls = urls;
_listener = listener;
_files = new FileObject[_urls.length];
final String[] finalFilenames = new String[_files.length];
for (int i = 0; i < urls.length; i++) {
final String filename = targetFilenames[i];
try {
_files[i] = targetDirectory.resolveFile(filename);
// slight differences may exist between target filename and
// actual filename. This trick will eradicate that.
finalFilenames[i] = _files[i].getName().getBaseName();
} catch (final FileSystemException e) {
// should never happen
throw new IllegalStateException(e);
}
}
final Action<Void> cancelCallback = arg0 -> cancelDownload();
_downloadProgressWindow = new FileTransferProgressWindow(windowContext, cancelCallback, finalFilenames);
_httpClient = httpClient;
}
private static FileObject createTargetDirectory(final UserPreferences userPreferences) {
final File localDirectory = userPreferences.getSaveDownloadedFilesDirectory();
return VFSUtils.toFileObject(localDirectory);
}
public static String[] createTargetFilenames(final String[] urls) {
final String[] filenames = new String[urls.length];
for (int i = 0; i < urls.length; i++) {
final String url = urls[i];
if (url == null) {
throw new IllegalArgumentException("urls[" + i + "] cannot be null");
}
final String filename = url.substring(url.lastIndexOf('/') + 1);
filenames[i] = filename;
}
return filenames;
}
public FileObject[] getFiles() throws SSLPeerUnverifiedException, IllegalStateException, RuntimeException {
try {
get();
} catch (Throwable e) {
if (e instanceof ExecutionException) {
e = e.getCause();
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
if (e instanceof SSLPeerUnverifiedException) {
throw (SSLPeerUnverifiedException) e;
}
throw new IllegalStateException(e);
}
return _files;
}
@Override
public void actionPerformed(final ActionEvent e) {
_downloadProgressWindow.setVisible(true);
execute();
}
@Override
protected void process(final List<Task> chunks) {
for (final Task task : chunks) {
try {
task.execute();
} catch (final Exception e) {
WidgetUtils.showErrorMessage("Error processing transfer chunk: " + task, e);
}
}
}
/**
* Cancels the file download gracefully.
*/
public void cancelDownload() {
cancelDownload(false);
}
/**
* Cancels the file download.
*
* @param hideWindowImmediately
* determines if the download progress window should be hidden
* immediately or only when the progress of cancelling the
* download has occurred gracefully.
*/
public void cancelDownload(final boolean hideWindowImmediately) {
logger.info("Cancel of download requested");
_cancelled = true;
if (hideWindowImmediately && _downloadProgressWindow != null) {
WidgetUtils.invokeSwingAction(_downloadProgressWindow::close);
}
}
@Override
protected void done() {
super.done();
if (!_cancelled) {
try {
final FileObject[] files = get();
if (_listener != null) {
_listener.onFilesDownloaded(files);
}
} catch (final Throwable e) {
if (_listener == null) {
// when there is no listener, the error will be catched and
// handled by the blocking getFiles() call.
return;
}
WidgetUtils.showErrorMessage("Error transfering file(s)!", e);
}
}
}
@Override
protected FileObject[] doInBackground() throws Exception {
for (int i = 0; i < _urls.length; i++) {
final String url = _urls[i];
final FileObject file = _files[i];
InputStream inputStream = null;
OutputStream outputStream = null;
try {
final byte[] buffer = new byte[1024];
final HttpGet method = new HttpGet(url);
if (!_cancelled) {
final HttpResponse response = _httpClient.execute(method);
if (response.getStatusLine().getStatusCode() != 200) {
throw new InvalidHttpResponseException(url, response);
}
final HttpEntity responseEntity = response.getEntity();
final long expectedSize = responseEntity.getContentLength();
if (expectedSize > 0) {
publish((Task) () -> _downloadProgressWindow
.setExpectedSize(file.getName().getBaseName(), expectedSize));
}
inputStream = responseEntity.getContent();
if (!file.exists()) {
file.createFile();
}
outputStream = file.getContent().getOutputStream();
long bytes = 0;
for (int numBytes = inputStream.read(buffer); numBytes != -1; numBytes = inputStream.read(buffer)) {
if (_cancelled) {
break;
}
outputStream.write(buffer, 0, numBytes);
bytes += numBytes;
final long totalBytes = bytes;
publish((Task) () -> _downloadProgressWindow
.setProgress(file.getName().getBaseName(), totalBytes));
}
if (!_cancelled) {
publish((Task) () -> _downloadProgressWindow.setFinished(file.getName().getBaseName()));
}
}
} catch (final IOException e) {
logger.debug("IOException occurred while downloading files", e);
throw e;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (final IOException e) {
logger.warn("Could not close input stream: " + e.getMessage(), e);
}
}
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (final IOException e) {
logger.warn("Could not flush & close output stream: " + e.getMessage(), e);
}
}
_httpClient.close();
}
if (_cancelled) {
logger.info("Deleting non-finished download-file '{}'", file);
file.delete();
}
}
return _files;
}
}