Search code examples
javaswingfile-iofilesizejprogressbar

Accurately summing file sizes to use in a JProgressBar


I have a list of files that I'm going to copy over to another location and I want to use a JProgressBar to monitor the progress of the transfer.

The JProgressBar constructor only takes an int for the bounds and File.length returns a long. I know I can cast the long to an int and lose some accuracy, but is this the correct way to do it?

I didn't include any code because it's not really a syntax or coding issue, I'm just not sure if casting a long to an int for use in a progress bar is the correct or accurate enough for my purposes.


Solution

  • I don't really see a problem about progress bar bounds just because actual progress is a percentual value from 0 to 100. You should always be capable to calculate the proportion between processed bytes and the total amount of bytes and that's the actual progress.

    I think the right question would be something like: how do I properly update the progress of my progress bar based on files length and total of bytes to be transferred?

    Consider this example:

    import java.awt.event.ActionEvent;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.io.File;
    import java.util.List;
    import javax.swing.AbstractAction;
    import javax.swing.Action;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JProgressBar;
    import javax.swing.SwingUtilities;
    import javax.swing.SwingWorker;
    
    public class Demo {
    
        private void createAndShowGUI() {
    
            final JProgressBar progressBar = new JProgressBar();
            progressBar.setStringPainted(true);
            progressBar.setString("");
    
            final SwingWorker<Void,String> worker = new SwingWorker<Void, String>() {
                @Override
                protected Void doInBackground() throws Exception {
    
                    long totalSize = 0l;
    
                    File[] files = new File(System.getProperty("user.dir")).listFiles();
                    for (File file : files) {
                        totalSize += file.length();
                    }
    
                    long transferred = 0l;
    
                    for (File file : files) {                    
                        Thread.sleep(250); // Just for animation purpose
                        transferred += file.length();
                        int progress = (int) (transferred * 100l / totalSize);
                        setProgress(progress);
    
                        String text = String.format("%1s%%: %2s / %3s bytes", progress, transferred, totalSize);
                        publish(text);
                    }
    
                    return null;
                }
    
                @Override
                protected void process(List<String> chunks) {
                    progressBar.setString(chunks.get(chunks.size() - 1));
                }
            };
    
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("progress".equals(evt.getPropertyName())) {
                        progressBar.setValue((Integer)evt.getNewValue());
                    }
                }
            });
    
            Action startAction = new AbstractAction("Start") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    worker.execute();
                    setEnabled(false);
                }
            };
    
            JPanel content = new JPanel();
            content.add(progressBar);
            content.add(new JButton(startAction));
    
            JFrame frame = new JFrame("Demo");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.add(content);
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
    
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new Demo().createAndShowGUI();
                }
            });
        }    
    }
    

    Because transferred * 100l / totalSize will be always a value between 0 and 100, you maybe will lose a decimal point accuracy but you definitely won't lose precision casting it to int in order to set progress bar value.