/*
GNU GENERAL LICENSE
Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution
This program 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
verion 3 of the License, or (at your option) any later version.
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
General License for more details.
You should have received a copy of the GNU General Public
along with this program. If not, see <http://www.gnu.org/licenses/>.
Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
*/
package org.lobobrowser.primary.gui.download;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import org.lobobrowser.clientlet.ClientletException;
import org.lobobrowser.clientlet.ClientletRequest;
import org.lobobrowser.clientlet.ClientletResponse;
import org.lobobrowser.gui.DefaultWindowFactory;
import org.lobobrowser.primary.gui.FieldType;
import org.lobobrowser.primary.gui.FormField;
import org.lobobrowser.primary.gui.FormPanel;
import org.lobobrowser.primary.settings.ToolsSettings;
import org.lobobrowser.request.AbstractRequestHandler;
import org.lobobrowser.request.ClientletRequestImpl;
import org.lobobrowser.request.RequestEngine;
import org.lobobrowser.request.RequestHandler;
import org.lobobrowser.ua.ProgressType;
import org.lobobrowser.ua.RequestType;
import org.lobobrowser.util.OS;
import org.lobobrowser.util.Timing;
/**
* The Class DownloadDialog.
*/
public class DownloadDialog extends JFrame {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The Constant logger. */
private static final Logger logger = LogManager.getLogger(DownloadDialog.class);
/** The progress bar. */
private final JProgressBar progressBar = new JProgressBar();
/** The bottom form panel. */
private final FormPanel bottomFormPanel = new FormPanel();
/** The top form panel. */
private final FormPanel topFormPanel = new FormPanel();
/** The document field. */
private final FormField documentField = new FormField(FieldType.TEXT, false);
/** The size field. */
private final FormField sizeField = new FormField(FieldType.TEXT, false);
/** The destination field. */
private final FormField destinationField = new FormField(FieldType.TEXT, false);
/** The time left field. */
private final FormField timeLeftField = new FormField(FieldType.TEXT, false);
/** The mime type field. */
private final FormField mimeTypeField = new FormField(FieldType.TEXT, false);
/** The transfer rate field. */
private final FormField transferRateField = new FormField(FieldType.TEXT, false);
/** The transfer size field. */
private final FormField transferSizeField = new FormField(FieldType.TEXT, false);
/** The save button. */
private final JButton saveButton = new JButton();
/** The close button. */
private final JButton closeButton = new JButton();
/** The open folder button. */
private final JButton openFolderButton = new JButton();
/** The open button. */
private final JButton openButton = new JButton();
/** The url. */
private final URL url;
/** The known content length. */
private final int knownContentLength;
/**
* Instantiates a new download dialog.
*
* @param response
* the response
* @param url
* the url
* @param transferSpeed
* the transfer speed
*/
public DownloadDialog(ClientletResponse response, URL url, int transferSpeed) {
this.url = url;
this.setIconImage(DefaultWindowFactory.getInstance().getDefaultImageIcon().getImage());
this.topFormPanel.setMinLabelWidth(100);
this.bottomFormPanel.setMinLabelWidth(100);
this.bottomFormPanel.setEnabled(false);
this.documentField.setCaption("Document:");
this.timeLeftField.setCaption("Estimated time:");
this.mimeTypeField.setCaption("MIME type:");
this.sizeField.setCaption("Size:");
this.destinationField.setCaption("File:");
this.transferSizeField.setCaption("Transfer size:");
this.transferRateField.setCaption("Transfer rate:");
this.openFolderButton.setVisible(false);
this.openButton.setVisible(false);
this.documentField.setValue(url.toExternalForm());
this.mimeTypeField.setValue(response.getMimeType());
int cl = response.getContentLength();
this.knownContentLength = cl;
String sizeText = cl == -1 ? "Not known" : getSizeText(cl);
this.sizeField.setValue(sizeText);
String estTimeText = (transferSpeed <= 0) || (cl == -1) ? "Not known"
: Timing.getElapsedText(cl / transferSpeed);
this.timeLeftField.setValue(estTimeText);
Container contentPane = this.getContentPane();
contentPane.setLayout(new FlowLayout());
Box rootPanel = new Box(BoxLayout.Y_AXIS);
rootPanel.setBorder(new EmptyBorder(4, 8, 4, 8));
rootPanel.add(this.progressBar);
rootPanel.add(Box.createVerticalStrut(8));
rootPanel.add(this.topFormPanel);
rootPanel.add(Box.createVerticalStrut(8));
rootPanel.add(this.bottomFormPanel);
rootPanel.add(Box.createVerticalStrut(8));
rootPanel.add(this.getButtonsPanel());
contentPane.add(rootPanel);
FormPanel bfp = this.bottomFormPanel;
bfp.addField(this.destinationField);
bfp.addField(this.transferRateField);
bfp.addField(this.transferSizeField);
FormPanel tfp = this.topFormPanel;
tfp.addField(this.documentField);
tfp.addField(this.mimeTypeField);
tfp.addField(this.sizeField);
tfp.addField(this.timeLeftField);
Dimension topPanelPs = this.topFormPanel.getPreferredSize();
this.topFormPanel.setPreferredSize(new Dimension(400, topPanelPs.height));
Dimension bottomPanelPs = this.bottomFormPanel.getPreferredSize();
this.bottomFormPanel.setPreferredSize(new Dimension(400, bottomPanelPs.height));
this.progressBar.setEnabled(false);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
RequestHandler rh = requestHandler;
if (rh != null) {
rh.cancel();
// So that there's no error dialog
requestHandler = null;
}
}
});
}
/**
* Gets the buttons panel.
*
* @return the buttons panel
*/
private Component getButtonsPanel() {
JButton saveButton = this.saveButton;
saveButton.setAction(new SaveAction());
saveButton.setText("Save As...");
saveButton.setToolTipText("You must select a file before download begins.");
JButton closeButton = this.closeButton;
closeButton.setAction(new CloseAction());
closeButton.setText("Cancel");
JButton openButton = this.openButton;
openButton.setAction(new OpenAction());
openButton.setText("Open");
JButton openFolderButton = this.openFolderButton;
openFolderButton.setAction(new OpenFolderAction());
openFolderButton.setText("Open Folder");
Box box = new Box(BoxLayout.X_AXIS);
// box.setBorder(new BevelBorder(BevelBorder.RAISED));
box.add(Box.createGlue());
box.add(openButton);
box.add(Box.createHorizontalStrut(4));
box.add(openFolderButton);
box.add(Box.createHorizontalStrut(4));
box.add(saveButton);
box.add(Box.createHorizontalStrut(4));
box.add(closeButton);
return box;
}
/**
* Select file.
*/
private void selectFile() {
String path = this.url.getPath();
int lastSlashIdx = path.lastIndexOf('/');
String tentativeName = lastSlashIdx == -1 ? path : path.substring(lastSlashIdx + 1);
JFileChooser chooser = new JFileChooser();
ToolsSettings settings = ToolsSettings.getInstance();
File directory = settings.getDownloadDirectory();
if (directory != null) {
File selectedFile = new File(directory, tentativeName);
chooser.setSelectedFile(selectedFile);
}
if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
if (file.exists()) {
if (JOptionPane.showConfirmDialog(this, "The file exists. Are you sure you want to overwrite it?",
"Confirm", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
return;
}
}
settings.setDownloadDirectory(file.getParentFile());
settings.save();
this.startDownload(chooser.getSelectedFile());
}
}
/** The request handler. */
private RequestHandler requestHandler;
/** The destination file. */
private File destinationFile;
/** The download base timestamp. */
private long downloadBaseTimestamp;
/** The last timestamp. */
private long lastTimestamp;
/** The last progress value. */
private long lastProgressValue;
/** The last transfer rate. */
private double lastTransferRate = Double.NaN;
/**
* Start download.
*
* @param file
* the file
*/
private void startDownload(File file) {
this.saveButton.setEnabled(false);
this.timeLeftField.setCaption("Time left:");
this.destinationField.setValue(file.getName());
this.destinationField.setToolTip(file.getAbsolutePath());
this.bottomFormPanel.setEnabled(true);
this.bottomFormPanel.revalidate();
ClientletRequest request = new ClientletRequestImpl(this.url, RequestType.DOWNLOAD);
RequestHandler handler = new DownloadRequestHandler(request, this, file);
this.destinationFile = file;
this.requestHandler = handler;
this.downloadBaseTimestamp = System.currentTimeMillis();
Thread t = new Thread(new DownloadRunnable(handler), "Download:" + this.url.toExternalForm());
t.setDaemon(true);
t.start();
}
/**
* Done with download_ safe.
*
* @param totalSize
* the total size
*/
private void doneWithDownload_Safe(final long totalSize) {
if (SwingUtilities.isEventDispatchThread()) {
doneWithDownload(totalSize);
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
doneWithDownload(totalSize);
}
});
}
}
/**
* Done with download.
*
* @param totalSize
* the total size
*/
private void doneWithDownload(long totalSize) {
this.requestHandler = null;
this.setTitle(this.destinationField.getValue());
this.timeLeftField.setCaption("Download time:");
long elapsed = System.currentTimeMillis() - this.downloadBaseTimestamp;
this.timeLeftField.setValue(Timing.getElapsedText(elapsed));
String sizeText = getSizeText(totalSize);
this.transferSizeField.setValue(sizeText);
this.sizeField.setValue(sizeText);
if (elapsed > 0) {
double transferRate = (double) totalSize / elapsed;
this.transferRateField.setValue(round1(transferRate) + " Kb/sec");
} else {
this.transferRateField.setValue("N/A");
}
this.progressBar.setIndeterminate(false);
this.progressBar.setStringPainted(true);
this.progressBar.setValue(100);
this.progressBar.setMaximum(100);
this.progressBar.setString("Done.");
this.closeButton.setText("Close");
if (OS.supportsLaunchPath()) {
this.saveButton.setVisible(false);
this.openFolderButton.setVisible(true);
this.openButton.setVisible(true);
this.openButton.revalidate();
}
}
/**
* Error in download_ safe.
*/
private void errorInDownload_Safe() {
if (SwingUtilities.isEventDispatchThread()) {
errorInDownload();
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
errorInDownload();
}
});
}
}
/**
* Error in download.
*/
private void errorInDownload() {
if (this.requestHandler != null) {
// If requestHandler is null, it means the download was explicitly
// cancelled or the window closed.
JOptionPane.showMessageDialog(this, "An error occurred while trying to download the file.");
this.dispose();
}
}
/**
* Round1.
*
* @param value
* the value
* @return the double
*/
private static double round1(double value) {
return Math.round(value * 10.0) / 10.0;
}
/**
* Gets the size text.
*
* @param numBytes
* the num bytes
* @return the size text
*/
private static String getSizeText(long numBytes) {
if (numBytes < 1024) {
return numBytes + " bytes";
} else {
double numK = numBytes / 1024.0;
if (numK < 1024) {
return round1(numK) + " Kb";
} else {
double numM = numK / 1024.0;
if (numM < 1024) {
return round1(numM) + " Mb";
} else {
double numG = numM / 1024.0;
return round1(numG) + " Gb";
}
}
}
}
/**
* Update progress_ safe.
*
* @param progressType
* the progress type
* @param value
* the value
* @param max
* the max
*/
private void updateProgress_Safe(final ProgressType progressType, final int value, final int max) {
if (SwingUtilities.isEventDispatchThread()) {
updateProgress(progressType, value, max);
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateProgress(progressType, value, max);
}
});
}
}
/**
* Update progress.
*
* @param progressType
* the progress type
* @param value
* the value
* @param max
* the max
*/
private void updateProgress(ProgressType progressType, int value, int max) {
String sizeText = getSizeText(value);
this.transferSizeField.setValue(sizeText);
long newTimestamp = System.currentTimeMillis();
double lastTransferRate = this.lastTransferRate;
long lastProgressValue = this.lastProgressValue;
long lastTimestamp = this.lastTimestamp;
long elapsed = newTimestamp - lastTimestamp;
double newTransferRate = Double.NaN;
if (elapsed > 0) {
newTransferRate = (value - lastProgressValue) / elapsed;
if (!Double.isNaN(lastTransferRate)) {
// Weighed average
newTransferRate = (newTransferRate + (lastTransferRate * 5.0)) / 6.0;
}
}
if (!Double.isNaN(newTransferRate)) {
this.transferRateField.setValue(round1(newTransferRate) + " Kb/sec");
int cl = this.knownContentLength;
if ((cl > 0) && (newTransferRate > 0)) {
this.timeLeftField.setValue(Timing.getElapsedText((long) ((cl - value) / newTransferRate)));
}
}
this.lastTimestamp = newTimestamp;
this.lastProgressValue = value;
this.lastTransferRate = newTransferRate;
JProgressBar pb = this.progressBar;
if (progressType == ProgressType.CONNECTING) {
pb.setIndeterminate(true);
pb.setStringPainted(true);
pb.setString("Connecting...");
this.setTitle(this.destinationField.getValue() + ": Connecting...");
} else if (max <= 0) {
pb.setIndeterminate(true);
pb.setStringPainted(false);
this.setTitle(sizeText + " " + this.destinationField.getValue());
} else {
int percent = (value * 100) / max;
pb.setIndeterminate(false);
pb.setStringPainted(true);
pb.setMaximum(max);
pb.setValue(value);
String percentText = percent + "%";
pb.setString(percentText);
this.setTitle(percentText + " " + this.destinationField.getValue());
}
}
/**
* The Class SaveAction.
*/
private class SaveAction extends AbstractAction {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.
* ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
selectFile();
}
}
/**
* The Class OpenFolderAction.
*/
private class OpenFolderAction extends AbstractAction {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.
* ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
File file = destinationFile;
if (file != null) {
try {
OS.launchPath(file.getParentFile().getAbsolutePath());
} catch (Exception thrown) {
logger.error("Unable to open folder of file: " + file + ".", thrown);
JOptionPane.showMessageDialog(DownloadDialog.this, "An error occurred trying to open the folder.");
}
}
}
}
/**
* The Class OpenAction.
*/
private class OpenAction extends AbstractAction {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.
* ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
File file = destinationFile;
if (file != null) {
try {
OS.launchPath(file.getAbsolutePath());
DownloadDialog.this.dispose();
} catch (Exception thrown) {
logger.error("Unable to open file: " + file + ".", thrown);
JOptionPane.showMessageDialog(DownloadDialog.this, "An error occurred trying to open the file.");
}
}
}
}
/**
* The Class CloseAction.
*/
private class CloseAction extends AbstractAction {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.
* ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
// windowClosedEvent takes care of cancelling download.
DownloadDialog.this.dispose();
}
}
/**
* The Class DownloadRunnable.
*/
private class DownloadRunnable implements Runnable {
/** The handler. */
private final RequestHandler handler;
/**
* Instantiates a new download runnable.
*
* @param handler
* the handler
*/
public DownloadRunnable(RequestHandler handler) {
this.handler = handler;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
RequestEngine.getInstance().inlineRequest(this.handler);
} catch (Exception err) {
logger.log(Level.ERROR, "Unexpected error on download of [" + url.toExternalForm() + "].", err);
}
}
}
/**
* The Class DownloadRequestHandler.
*/
private class DownloadRequestHandler extends AbstractRequestHandler {
/** The file. */
private final File file;
/** The download done. */
private boolean downloadDone = false;
/** The last progress update. */
private long lastProgressUpdate = 0;
/**
* Instantiates a new download request handler.
*
* @param request
* the request
* @param dialogComponent
* the dialog component
* @param file
* the file
*/
public DownloadRequestHandler(ClientletRequest request, Component dialogComponent, File file) {
super(request, dialogComponent);
this.file = file;
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.request.AbstractRequestHandler#handleException(org.
* lobobrowser .clientlet.ClientletResponse, java.lang.Throwable)
*/
@Override
public boolean handleException(ClientletResponse response, Throwable exception) throws ClientletException {
logger.error(
"An error occurred trying to download " + response.getResponseURL() + " to " + this.file + ".",
exception);
errorInDownload_Safe();
return true;
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.request.AbstractRequestHandler#handleProgress(org.
* lobobrowser .ua.ProgressType, java.net.URL, java.lang.String, int,
* int)
*/
@Override
public void handleProgress(ProgressType progressType, URL url, String method, int value, int max) {
if (!this.downloadDone) {
long timestamp = System.currentTimeMillis();
if ((timestamp - this.lastProgressUpdate) > 1000) {
updateProgress_Safe(progressType, value, max);
this.lastProgressUpdate = timestamp;
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.request.AbstractRequestHandler#processResponse(org.
* lobobrowser .clientlet.ClientletResponse)
*/
@Override
public void processResponse(ClientletResponse response) throws ClientletException, IOException {
OutputStream out = new FileOutputStream(this.file);
try {
InputStream in = response.getInputStream();
try {
int totalRead = 0;
byte[] buffer = new byte[8192];
int numRead;
while ((numRead = in.read(buffer)) != -1) {
if (this.isCancelled()) {
throw new IOException("cancelled");
}
totalRead += numRead;
out.write(buffer, 0, numRead);
}
this.downloadDone = true;
doneWithDownload_Safe(totalRead);
} finally {
in.close();
}
} finally {
out.close();
}
}
}
}