Search code examples
javadebuggingdownloadswingworker

Why isn't my image downloading when I use a SwingWorker but downloads when I don't?


I'm creating a progress bar to monitor an image download. The image download doesn't work — it generates a file measuring 0 bytes in size. If I move my code to a standalone class without a SwingWorker, the image download works. I've played with this for a while and I still don't know what I'm doing wrong. The two code blocks are identical. Any tips would be much appreciated!

Image download with SwingWorker (take note of doInBackground):

package download_progress_bar;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.net.*;
import java.io.*;

public class ProgressBar implements ActionListener, PropertyChangeListener {
    private JFrame frame;
    private JPanel gui;
    private JButton button;
    private JProgressBar progressBar;
    private SwingWorker<Void, Void> worker;
    private boolean done;

    public ProgressBar() {
        done = false;
        customizeFrame();
        createMainPanel();
        createProgressBar();
        createButton();
        addComponentsToFrame();
    }

    private void customizeFrame() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private void createMainPanel() {
        gui = new JPanel();
        gui.setLayout(new BorderLayout());
    }

    private void createProgressBar() {
        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);  // renders a progress string
    }

    private void createButton()  {
        button = new JButton("Start download");
        button.addActionListener(this);
    }

    /**
     * Invoked when user clicks the button.
     */
    public void actionPerformed(ActionEvent evt) {
        button.setEnabled(false);
        // NOTE: Instances of javax.swing.SwingWorker are not reusable, 
        // so we create new instances as needed
        worker = new Worker();
        worker.addPropertyChangeListener(this);
        worker.execute();
    }

    class Worker extends SwingWorker<Void, Void> {
        /* 
         * Main task. Executed in worker thread.
         */
        @Override
        protected Void doInBackground() throws MalformedURLException {
            // Create a URL object for a given URL
            String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
            URL url = new URL(src);
            // Open connection on the URL object

            try {
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                // Always check response code first
                int responseCode = connection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    System.out.println(responseCode);

                    // Open input stream from connection
                    BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
                    // Open output stream for file writing
                    BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));

                    int bytesRead = - 1;
                    int totalBytesRead = 0;
                    int percentCompleted = 0;

                    while ((bytesRead = in.read()) != -1) {
                        out.write(bytesRead);
                        totalBytesRead += bytesRead;
                        percentCompleted = totalBytesRead * 100 / connection.getContentLength();

                        System.out.println("..." + percentCompleted);
                        this.setProgress(percentCompleted);
                    }

                    // Close streams
                    out.close();
                    in.close();
                }
            } catch (IOException ex) {
                System.out.println(ex);
                this.setProgress(0);
                cancel(true);
            }

            return null;
        }

        /*
         * Executed in event dispatching thread
         */
        @Override
        protected void done() {
            button.setEnabled(true);
            if (!isCancelled()) {
                System.out.println("File has been downloaded successfully!");
            } else {
                System.out.println("There was an error in downloading the file.");
            }
        }
    }

    /**
     * Invoked when task's progress property changes.
     */
    public void propertyChange(PropertyChangeEvent evt) {
        System.out.println(evt);
        // NOTE: By default two property states exist: "state" and "progress"
        if (evt.getPropertyName().equals("progress")) {
            int progress = (Integer) evt.getNewValue();
            progressBar.setValue(progress);
            System.out.println(String.format(
                    "Completed %d%% of task.\n", progress));
        }
    }

    private void addComponentsToFrame() {
        gui.add(progressBar, BorderLayout.CENTER);
        gui.add(button, BorderLayout.SOUTH);
        frame.add(gui);
        frame.pack();
    }

    public void activate() {
        frame.setVisible(true);
    }
}

Image download with standalone Downloader class (take note of download):

package download_progress_bar;

import java.net.*;
import java.io.*;

public class Downloader {
    public static void main(String[] args) throws IOException {
        download();
    }

    public static void download() throws IOException {
        // Create a URL object for a given URL
        String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
        URL url = new URL(src);
        // Open connection on the URL object

        try {
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Always check response code first
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                System.out.println(responseCode);

                // Open input stream from connection
                BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
                // Open output stream for file writing
                BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("test.jpg"));

                int bytesRead = - 1;
                int totalBytesRead = 0;
                int percentCompleted = 0;

                while ((bytesRead = in.read()) != -1) {
                    out.write(bytesRead);
                    totalBytesRead += bytesRead;
                    percentCompleted = totalBytesRead * 100 / connection.getContentLength();

                    System.out.println("..." + percentCompleted);
                }

                // Close streams
                out.close();
                in.close();
            }
        } catch (IOException ex) {
            System.out.println(ex);
        }
    }
}

Solution

  • You should call get() in done(). If doInBackground throws an exception, then get() will throw an ExecutionException whose cause is the exception from doInBackground.

    Something like this:

    @Override
    protected void done() {
        button.setEnabled(true);
        try {
            if (!isCancelled()) {
                get();
                System.out.println("File has been downloaded successfully!");
                return;
            }
        } catch (InterruptedException x) {
            x.printStackTrace();
        } catch (ExecutionException x) {
            // This should print an IllegalArgumentException
            // if me theory (explained below) is correct.
            x.getCause().printStackTrace();
        }
        System.out.println("There was an error in downloading the file.");
    }
    

    My theory is that the issue is related to this line:

    totalBytesRead += bytesRead;
    

    Since bytesRead is the return value of InputStream.read(), it's actually a byte of data, not the number of bytes read. This doesn't have an obvious effect on the I/O, but it corrupts the value of percentCompleted. This ends up passing a value which is greater than 100 to setProgress which throws an exception. The line should be totalBytesRead++; instead.

    You can verify my theory with the aforementioned modification to done().