Java Swing application to download files from FTP server with progress bar
- Details
- Written by Nam Ha Minh
- Last Updated on 04 May 2023   |   Print Email
In the article Download files from a FTP server, we presented a console-based application that demonstrates how to download remote files from a FTP server. Based on that, we will go further by developing a Swing-based version of the application in this tutorial. The application looks like this:
The following information is required in order to download a remote file from the FTP server and save it on the local computer:
- Host: Host name or IP address of the FTP server.
- Port: FTP port number (default is 21).
- Username: user name of an account on the FTP server.
- Password: password of the account.
- Download path: Full path of the file needs to be downloaded on the server, for example: /Projects/Java/FTP.zip
- A directory on the local computer where the file will be stored.
The following diagram explains workflow of the application:
The following class diagram shows how the application is designed:
There are three main classes:
- FTPUtility: implements main functions to work with the FTP server: connect, download, disconnect, etc.
- DownloadTask: executes the file download in a background thread so that the GUI won’t become unresponsive or freezing.
- SwingFileDownloadFTP: constructs user interface of the application which allows users to specify the information mentioned above to download the file, and updates the progress bar while the download is taking place.
Beside these main classes, the JFilePicker and FileTypeFilter classes are used to show a directory chooser dialog. Its source code can be obtained from article File picker component in Swing. The FTPException is just a simple custom exception class.
Let’s see how each main class is implemented.
1. Code of the FTPUtility class
package net.codejava.swing.download.ftp; import java.io.IOException; import java.io.InputStream; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; /** * A utility class that provides functionality for downloading files from a FTP * server. * * @author www.codejava.net * */ public class FTPUtility { // FTP server information private String host; private int port; private String username; private String password; private FTPClient ftpClient = new FTPClient(); private int replyCode; private InputStream inputStream; public FTPUtility(String host, int port, String user, String pass) { this.host = host; this.port = port; this.username = user; this.password = pass; } /** * Connect and login to the server. * * @throws FTPException */ public void connect() throws FTPException { try { ftpClient.connect(host, port); replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { throw new FTPException("FTP serve refused connection."); } boolean logged = ftpClient.login(username, password); if (!logged) { // failed to login ftpClient.disconnect(); throw new FTPException("Could not login to the server."); } ftpClient.enterLocalPassiveMode(); } catch (IOException ex) { throw new FTPException("I/O error: " + ex.getMessage()); } } /** * Gets size (in bytes) of the file on the server. * * @param filePath * Path of the file on server * @return file size in bytes * @throws FTPException */ public long getFileSize(String filePath) throws FTPException { try { FTPFile file = ftpClient.mlistFile(filePath); if (file == null) { throw new FTPException("The file may not exist on the server!"); } return file.getSize(); } catch (IOException ex) { throw new FTPException("Could not determine size of the file: " + ex.getMessage()); } } /** * Start downloading a file from the server * * @param downloadPath * Full path of the file on the server * @throws FTPException * if client-server communication error occurred */ public void downloadFile(String downloadPath) throws FTPException { try { boolean success = ftpClient.setFileType(FTP.BINARY_FILE_TYPE); if (!success) { throw new FTPException("Could not set binary file type."); } inputStream = ftpClient.retrieveFileStream(downloadPath); if (inputStream == null) { throw new FTPException( "Could not open input stream. The file may not exist on the server."); } } catch (IOException ex) { throw new FTPException("Error downloading file: " + ex.getMessage()); } } /** * Complete the download operation. */ public void finish() throws IOException { inputStream.close(); ftpClient.completePendingCommand(); } /** * Log out and disconnect from the server */ public void disconnect() throws FTPException { if (ftpClient.isConnected()) { try { if (!ftpClient.logout()) { throw new FTPException("Could not log out from the server"); } ftpClient.disconnect(); } catch (IOException ex) { throw new FTPException("Error disconnect from the server: " + ex.getMessage()); } } } /** * Return InputStream of the remote file on the server. */ public InputStream getInputStream() { return inputStream; } }
The steps of method invocation to use this utility class to download a file as follows:
- Call connect(): to connect and login to the server.
- Call getFileSize(): to get size of file before downloading. This is needed in order to show correct progress of the download.
- Call getInputStream(): to open input stream of the file being downloaded. The client will use the input stream to read the file’s data from the server.
- Call finish(): to complete the file transfer session with the server.
- Call disconnect(): to logout and close the connection with the server.
2. Code of the DownloadTask class
package net.codejava.swing.download.ftp; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import javax.swing.JOptionPane; import javax.swing.SwingWorker; /** * Execute file download in a background thread and update the progress. * * @author www.codejava.net * */ public class DownloadTask extends SwingWorker<Void, Void> { private static final int BUFFER_SIZE = 4096; private String host; private int port; private String username; private String password; private String downloadPath; private String saveDir; private SwingFileDownloadFTP gui; public DownloadTask(String host, int port, String username, String password, String downloadPath, String saveDir, SwingFileDownloadFTP gui) { this.host = host; this.port = port; this.username = username; this.password = password; this.downloadPath = downloadPath; this.saveDir = saveDir; this.gui = gui; } /** * Executed in background thread */ @Override protected Void doInBackground() throws Exception { FTPUtility util = new FTPUtility(host, port, username, password); try { util.connect(); byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = -1; long totalBytesRead = 0; int percentCompleted = 0; long fileSize = util.getFileSize(downloadPath); gui.setFileSize(fileSize); String fileName = new File(downloadPath).getName(); File downloadFile = new File(saveDir + File.separator + fileName); FileOutputStream outputStream = new FileOutputStream(downloadFile); util.downloadFile(downloadPath); InputStream inputStream = util.getInputStream(); while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; percentCompleted = (int) (totalBytesRead * 100 / fileSize); setProgress(percentCompleted); } outputStream.close(); util.finish(); } catch (FTPException ex) { JOptionPane.showMessageDialog(null, "Error downloading file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); setProgress(0); cancel(true); } finally { util.disconnect(); } return null; } /** * Executed in Swing's event dispatching thread */ @Override protected void done() { if (!isCancelled()) { JOptionPane.showMessageDialog(null, "File has been downloaded successfully!", "Message", JOptionPane.INFORMATION_MESSAGE); } } }
This class utilizes the FTPUtility class above to run the download in a background thread (code executed in the doInBackground() method). So the GUI won’t become freezing while the download is taking place, and the download progress is updated in the progress bar immediately. If any error occurred during the download, an error message dialog will appear. Finally, when the download completes, a successful message appears (the done() method is invoked).
3. Code of the SwingFileDownloadFTP class
package net.codejava.swing.download.ftp; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JProgressBar; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; import net.codejava.swing.JFilePicker; /** * A Swing application that downloads file from a FTP server. * * @author www.codejava.net * */ public class SwingFileDownloadFTP extends JFrame implements PropertyChangeListener { private JLabel labelHost = new JLabel("Host:"); private JLabel labelPort = new JLabel("Port:"); private JLabel labelUsername = new JLabel("Username:"); private JLabel labelPassword = new JLabel("Password:"); private JLabel labelDownloadPath = new JLabel("Download path:"); private JTextField fieldHost = new JTextField(40); private JTextField fieldPort = new JTextField(5); private JTextField fieldUsername = new JTextField(30); private JPasswordField fieldPassword = new JPasswordField(30); private JTextField fieldDownloadPath = new JTextField(30); private JFilePicker filePicker = new JFilePicker("Save file to: ", "Browse..."); private JButton buttonDownload = new JButton("Download"); private JLabel labelFileSize = new JLabel("File size (bytes):"); private JTextField fieldFileSize = new JTextField(15); private JLabel labelProgress = new JLabel("Progress:"); private JProgressBar progressBar = new JProgressBar(0, 100); public SwingFileDownloadFTP() { super("Swing File Download from FTP server"); // set up layout setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(5, 5, 5, 5); // set up components filePicker.setMode(JFilePicker.MODE_SAVE); filePicker.getFileChooser().setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY); fieldFileSize.setEditable(false); buttonDownload.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { buttonDownloadActionPerformed(event); } }); progressBar.setPreferredSize(new Dimension(200, 30)); progressBar.setStringPainted(true); // add components to the frame constraints.gridx = 0; constraints.gridy = 0; add(labelHost, constraints); constraints.gridx = 1; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weightx = 1.0; add(fieldHost, constraints); constraints.gridx = 0; constraints.gridy = 1; add(labelPort, constraints); constraints.gridx = 1; add(fieldPort, constraints); constraints.gridx = 0; constraints.gridy = 2; add(labelUsername, constraints); constraints.gridx = 1; add(fieldUsername, constraints); constraints.gridx = 0; constraints.gridy = 3; add(labelPassword, constraints); constraints.gridx = 1; add(fieldPassword, constraints); constraints.gridx = 0; constraints.gridy = 4; add(labelDownloadPath, constraints); constraints.gridx = 1; add(fieldDownloadPath, constraints); constraints.gridx = 0; constraints.gridwidth = 2; constraints.gridy = 5; constraints.anchor = GridBagConstraints.WEST; add(filePicker, constraints); constraints.gridx = 0; constraints.gridy = 6; constraints.anchor = GridBagConstraints.CENTER; constraints.fill = GridBagConstraints.NONE; add(buttonDownload, constraints); constraints.gridx = 0; constraints.gridy = 7; constraints.gridwidth = 1; constraints.anchor = GridBagConstraints.WEST; add(labelFileSize, constraints); constraints.gridx = 1; add(fieldFileSize, constraints); constraints.gridx = 0; constraints.gridy = 8; add(labelProgress, constraints); constraints.gridx = 1; constraints.fill = GridBagConstraints.HORIZONTAL; add(progressBar, constraints); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } /** * handle click event of the Download button */ private void buttonDownloadActionPerformed(ActionEvent event) { String host = fieldHost.getText(); int port = Integer.parseInt(fieldPort.getText()); String username = fieldUsername.getText(); String password = new String(fieldPassword.getPassword()); String downloadPath = fieldDownloadPath.getText(); String saveDir = filePicker.getSelectedFilePath(); progressBar.setValue(0); DownloadTask task = new DownloadTask(host, port, username, password, downloadPath, saveDir, this); task.addPropertyChangeListener(this); task.execute(); } /** * Update the progress bar's state whenever the progress of download * changes. */ @Override public void propertyChange(PropertyChangeEvent evt) { if ("progress" == evt.getPropertyName()) { int progress = (Integer) evt.getNewValue(); progressBar.setValue(progress); } } void setFileSize(long fileSize) { fieldFileSize.setText(String.valueOf(fileSize)); } /** * Launch the application */ public static void main(String[] args) { try { // set look and feel to system dependent UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { ex.printStackTrace(); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new SwingFileDownloadFTP().setVisible(true); } }); } }
This class displays the user interface and also acts as the main entry to launch the application. It wires all the pieces together to form a complete application. When the button Download is clicked, an instance of the DownloadTask class is created to carry out the download in a separate background thread other than the Swing event dispatching thread (EDT).
To get the progress bar’s state updated, this class implements the java.beans.PropertyChangeListener interface to acts as a listener of the DownloadTask. The DownloadTask class notifies progress of the upload by calling the setProgress() method.
4. Code of FTPException class
package net.codejava.swing.download.ftp; public class FTPException extends Exception { public FTPException(String message) { super(message); } }
5. Testing the application
Run the application by executing the SwingFileDownloadFTP class. Enter the required information and click Download button to start downloading the file:
When the file is downloaded and saved completely, a successful message dialog appears like this:
An error message dialog will appear in case of error, e.g. incorrect username/password:
Or if the download path is incorrect:
You can download full source code and executable jar file for this application in the Attachments section. Note that the application requires Java 1.6 or later. The project is also available on GitHub at this repo link.
Related Tutorials:
- Java FTP File Download Tutorial
- How to create File picker component in Swing
- Swing application to download files from HTTP server with progress bar
- Java Servlet Download File Example
Other Java Coding Tutorials:
- 10 Common Mistakes Every Beginner Java Programmer Makes
- 10 Java Core Best Practices Every Java Programmer Should Know
- How to become a good programmer? 13 tasks you should practice now
- How to calculate MD5 and SHA hash values in Java
- How to generate random numbers in Java
- Java File Encryption and Decryption Example
Comments
I added these two lines to the getFileSize(String) Method:
System.out.println(file);
System.out.println(file.getSize());
output:
Type=file;Size=100000000;Modify=20150718214323.522; a.part09.rar
-1
because of this, the getFileSize Method also returns -1 and the ftp client is not able to download anything.
i have absolutely no idea whats going on with the FTPFile
There is a tutorial for that: How to download a complete folder from a FTP server (codejava.net/.../...)
You can use the ideas of this article's Swing app to build your own one to download a folder.