Progress bar while copying files with Java
Asked Answered
F

4

14

I'm sure this question has been asked before, but none of the answers I found will work very well with my existing code. I'm posting this question in case there's a way to do it without completely redoing what I have so far.

The idea is to display a very basic progress bar while copying files and directories from one drive to another. I have a class called BasicCopy that is designed to copy the contents of the Pictures, Documents, videos, and Music folders (standard on Windows machines) to folders of the same names within a backup directory on a second drive. Here is the class so far:

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.io.FileUtils;

public class BasicCopy {

    String username = "";
    String mainDrive = "";
    String backupDrive = "";
    String backupDir = "";
    String[] directories;

    public BasicCopy(String inDrive, String outDrive, String username){
        mainDrive = inDrive;
        backupDrive = outDrive;
        this.username = username;

        createBackupDirectory();
        copyDirectories();

        close();
    }

    //Create backup directory
    public void createBackupDirectory(){
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
        String timestamp = sdf.format(date);
        backupDir = backupDrive + ":\\" + "Backup " + timestamp;
        File backupDirectory = new File(backupDir);
        backupDirectory.mkdir();
    }

    public void copyDirectories(){
        //Main directories
        String pics = mainDrive + ":\\Users\\" + username + "\\Pictures";
        String docs = mainDrive + ":\\Users\\" + username + "\\Documents";
        String vids = mainDrive + ":\\Users\\" + username + "\\Videos";
        String musc = mainDrive + ":\\Users\\" + username + "\\Music";
        //Backup directories
        String bkPics = backupDir + "\\Pictures";
        String bkDocs = backupDir + "\\Documents";
        String bkVids = backupDir + "\\Documents";
        String bkMusc = backupDir + "\\Pictures";

        String[] directories = {pics, docs, vids, musc};
        String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};

        //Loop through directories and copy files
        for (int i = 0; i < directories.length; i++){
            File src = new File(directories[i]);
            File dest = new File(bkDirectories[i]);
            try{
                FileUtils.copyDirectory(src, dest);
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    /* Close current process */
    public void close(){
        System.exit(0);
    }
}

I have a method in a previous class that measures the total size of the directories, so I could pass that in to this class if necessary. However, I currently loop through only the four directories, so I expect I wouldn't be able to increment a progress bar with any higher resolution than 25% per tick. I am wondering if there's a way I might change it so that I can include a progress bar to monitor it, and have it be a little more accurate? Furthermore, I'm not sure if this should be asked in a different thread or not, but this method of file copying takes a very long time. It takes hours to copy 500MB worth of files, and I was wondering if there might be a way to speed it up? That part isn't a priority though. Right now I'm mainly interested in adding in a progress bar. Cheers!

EDIT:

After some fiddling I realized I could probably use code similar to this (This exact code may or may not work--I just jotted it down quickly so I wouldn't forget it, it is still untested). This would allow me to update the progress bar for each file copied.

for (int i = 0; i < directories.length; i++){
    File dir = new File(directories[i]);
    File dest = new File(bkDirectories[i]);
    for(File file: dir.listFiles()){
        try{
            FileUtils.copyDirectory(file, dest);
            //update progress bar here
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

EDIT #2:

I have worked a bit more on the code and I believe I've figured most of it out. The question now is about a SwingWorker, which I believe I'll need in order to run the long-term methods in the background. Otherwise the GUI becomes unresponsive (lots of documentation on this in the Java docs). However, this is where I get stuck. I've only used a SwingWorker once before, and that was mainly with copied code. I am wondering how I could implement that using the following code so that the progress bar (and the rest of the frame) actually appears.

Updated code:

import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JLabel;

import org.apache.commons.io.FileUtils;

@SuppressWarnings("serial")
public class BasicCopy extends JFrame {

    private JPanel contentPane;
    private JTextArea txtCopiedDirs;
    private JButton btnCancel;
    private JProgressBar progressBar;
    private JLabel lblCopying;
    private String mainDrive;
    private String backupDrive;
    private String username;
    private String backupDir;
    long totalSize = 0; //total size of directories/files
    long currentSize = 0;   //current size of files counting up to ONE_PERCENT
    int currentPercent = 0; //current progress in %
    long ONE_PERCENT;       //totalSize / 100

    public BasicCopy() {
    }

    public BasicCopy(String inDrive, String outDrive, String username, long space){
        mainDrive = inDrive;
        backupDrive = outDrive;
        this.username = username;
        totalSize = space;
        ONE_PERCENT = totalSize/100;
        createGUI();

        /*  Should not have these here!
         *  Pretty sure I need a SwingWorker
         */
        createBackupDirectory();
        copyDirectories();
    }

    public void createGUI(){
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("Backup Progress");
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        txtCopiedDirs = new JTextArea();
        txtCopiedDirs.setBounds(10, 56, 414, 125);
        contentPane.add(txtCopiedDirs);

        btnCancel = new JButton("Cancel");
        btnCancel.setBounds(169, 227, 89, 23);
        contentPane.add(btnCancel);

        progressBar = new JProgressBar(0, 100);
        progressBar.setBounds(10, 192, 414, 24);
        progressBar.setValue(0);
        contentPane.add(progressBar);

        lblCopying = new JLabel("Now backing up your files....");
        lblCopying.setBounds(10, 11, 157, 34);
        contentPane.add(lblCopying);

        setVisible(true);
    }

    //Create backup directory
    public void createBackupDirectory(){
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
        String timestamp = sdf.format(date);
        backupDir = backupDrive + ":\\" + "Backup " + timestamp;
        File backupDirectory = new File(backupDir);
        backupDirectory.mkdir();
    }

    public void copyDirectories(){
        //Main directories
        String pics = mainDrive + ":\\Users\\" + username + "\\Pictures";
        String docs = mainDrive + ":\\Users\\" + username + "\\Documents";
        String vids = mainDrive + ":\\Users\\" + username + "\\Videos";
        String musc = mainDrive + ":\\Users\\" + username + "\\Music";
        //Backup directories
        String bkPics = backupDir + "\\Pictures";
        String bkDocs = backupDir + "\\Documents";
        String bkVids = backupDir + "\\Documents";
        String bkMusc = backupDir + "\\Pictures";

        String[] directories = {pics, docs, vids, musc};
        String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};

        //Loop through directories and copy files
        for (int i = 0; i < directories.length; i++){
            File dir = new File(directories[i]);
            File dest = new File(bkDirectories[i]);
            for(File file: dir.listFiles()){
                try{
                    FileUtils.copyDirectory(file, dest);
                    if(getDirSize(file) >= ONE_PERCENT){
                        currentPercent++;
                        progressBar.setValue(currentPercent);
                        currentSize = 0;
                    } else {
                        currentSize = currentSize + getDirSize(file);
                        if(currentSize >= ONE_PERCENT){
                            currentPercent++;
                            progressBar.setValue(currentPercent);
                            currentSize = 0;
                        }
                    }
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static Long getDirSize(File directory) {
        long size = 0L;

        if (directory.listFiles() != null){       
            for (File file : directory.listFiles()) {
                size += file.isDirectory() ? getDirSize(file) : file.length();
            }
        }
        return size;
    }

    /* Close current window */
    public void closeWindow() {
        WindowEvent close = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(close);
        System.exit(0);
    }
}
Flor answered 24/12, 2013 at 22:6 Comment(7)
"none of the answers I found will work very well with my existing code" Why not?Doorknob
They all require a complete re-write, requiring different methods to loop through the files, create directories, etc. I set my loops up the way I did for a reason, and was really hoping I could adapt the progress bar to work with it, since my code is running as it should.Flor
1) btnCancel.setBounds(169, 227, 89, 23); Where do you get your code examples from? The bottom line is that that is very poor code, and should not be done. I want to know what resource is leading people down this wrong path. 2) As general advice: Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Instead of calling Thread.sleep(n) implement a Swing Timer for repeating tasks or a SwingWorker for long running tasks. See Concurrency in Swing for more details.Doorknob
"and was really hoping I could adapt the progress bar to work with it" I was really hoping for a pony for XMas. We don't always get what we want..Doorknob
Haha, Yes, I understand I may not get it, I realize I may have to do a complete re-write after all, but I posted this question just in case there's a way I can do it using my existing code. Anyway, 1) parts of the layout (such as the cancel button code you mentioned in your previous comment) was created using WindowBuilder. I place the button where I want it on the layout tab, and it auto-generates the code for me. 2) I understand I shouldn't block the EDT, which is why I posted the code in my latest edit with a comment; I need to apply a swingworker instead of putting method calls in the EDTFlor
"was created using WindowBuilder" Huh.. I thought the GUI builders had become more sensible over time (by encouraging people to use the layouts). Java GUIs might have to work on a number of platforms, on different screen resolutions & using different PLAFs. As such they are not conducive to exact placement of components. To organize the components for a robust GUI, instead use layout managers, or combinations of them, along with layout padding & borders for white space.Doorknob
As a rule I generally only use WindowBuilder to get the basic layout, and once I get the code working properly I go back and modify it by hand, making it cleaner and more logical. I don't use WindowBuilder to do the final layout, I don't like having some half-witted plugin take the place of an actual human mind. Things just don't usually work out well that way.Flor
F
7

I have found a solution that works. It still has some minor glitches, but mainly with unimportant details. Here is what I have now, and it seems to be working.

class Task extends SwingWorker<Void, Void>{
    @Override
    public Void doInBackground(){
        setProgress(0);

        //Create Backup Directory
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
        String timestamp = sdf.format(date);
        backupDir = backupDrive + ":\\" + "Backup_" + timestamp;
        File backupDirectory = new File(backupDir);
        backupDirectory.mkdir();

        //Copy Files
        //Main directories
        String pics = mainDrive + ":\\Users\\" + username + "\\Pictures\\";
        String docs = mainDrive + ":\\Users\\" + username + "\\Documents\\";
        String vids = mainDrive + ":\\Users\\" + username + "\\Videos\\";
        String musc = mainDrive + ":\\Users\\" + username + "\\Music\\";
        //Backup directories
        String bkPics = backupDir + "\\Pictures\\";
        String bkDocs = backupDir + "\\Documents\\";
        String bkVids = backupDir + "\\Documents\\";
        String bkMusc = backupDir + "\\Pictures\\";

        String[] directories = {pics, docs, vids, musc};
        String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};

        //Loop through directories and copy files
        for (int i = 0; i < directories.length; i++){
            File dir = new File(directories[i]);
            File dest = new File(bkDirectories[i]);
            for(File file: dir.listFiles()){
                try{
                    if(file.isFile()){
                        FileUtils.copyFileToDirectory(file, dest);
                        txtCopiedDirs.append(file.getAbsolutePath() + "\n");
                    } else if (file.isDirectory()){
                        FileUtils.copyDirectoryToDirectory(file, dest);
                        txtCopiedDirs.append(file.getAbsolutePath() + "\n");
                    }
                    if(getDirSize(file) >= ONE_PERCENT){
                        currentPercent = getDirSize(file)/ONE_PERCENT;
                        progressBar.setValue((int)currentPercent);
                        currentSize = 0;
                    } else {
                        currentSize = currentSize + getDirSize(file);
                        if(currentSize >= ONE_PERCENT){
                            currentPercent = currentSize/ONE_PERCENT;
                            currentPercent++;
                            progressBar.setValue((int)currentPercent);
                            currentSize = 0;
                        }
                    }
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }

        return null;
    }
    @Override
    public void done(){
        closeWindow();
    }
}

This class is contained within the main class, and is started using the following code:

Task task = new Task();
task.execute();

This is called immediately after the frame is created and set visible.

Now, as I mentioned this still isn't perfect. For example, the check to see if it's a file or a directory before copying it should be replaced with a recursive function, so that it loops through each individual file, rather than just copy the directory if it's a full directory. This will allow for more accurate updating of the progress bar and will show the individual files being copied in the text area, rather than files and directories.

Anyway, I just wanted to post this code to show what has worked for me, without completely redoing what I had. I will keep working on it though, and post any important updates that may help future readers.

Thanks everyone for the responses, they've helped a lot!

Flor answered 26/12, 2013 at 2:9 Comment(1)
You do realise that this example violates the single thread rules of swing by updating the progress bar directly from with the doInBackground method, which defeats the purpose of the using a SwingWorker. Better to use the property change support and update the progress bar through itGraupel
S
3

I'm assuming you want a graphical progress bar, and not a console based (tui) solution. Have you read this Swing tutorial?

EDIT: If you want one tick of your progress bar per file, you simply need to tell the progress bar constructor how many total ticks there are, something like:

progressBar = new JProgressBar(0, allFilesInAllDirectoriesLength);

and organize your for loop to work on each file, instead of looping on the directories.

Score answered 24/12, 2013 at 22:13 Comment(9)
You are correct, I'm looking for a progress bar with a GUI. However, my main question isn't really about creating the progress bar, it's about linking it to my code. I have read that tutorial, by the way, but thanks for the link nonetheless--I may need it for reference laterFlor
You should clarify in your question what you mean by 'linking' the two. Do you mean, you'd like a higher resolution than just one tick per file copy?Score
One tick per directory, yes. I would rather have one tick per file, for example. I tried to explain that basic concept (though no real detail) in this sentence: "I currently loop through only the four directories, so I expect I wouldn't be able to increment a progress bar with any higher resolution than 25% per tick."Flor
@derstrom8 use your own method to copy file check file size take 1 percent from it and each time you reach amount of bites/mb/ that represent 1 percent of that file update your progress barKerseymere
Excellent idea @TomasBisciak, Just the kind of ingenuity I was looking for! Not sure I would have every come up with that myself. If you'd like, you're welcome to post that as an answer. I'll try it out, and if I get it to work I'll accept it. Many thanks!Flor
Hmm, actually I have one further question--At what point would I check the total size? It seems I'd only be able to do it once for each iteration of the loop, which is still a 25% resolution?Flor
each time your buffer type something take amount and add it to some kind of counter.When you are in range of 1% or past it update your progressbar.You will need to make your own copyDir method.And yes you have to check file size only at start of copying.Not during.While you loop you only keep track of those bytes that already was copyed to another dir.Kerseymere
@AmirAfghani, regarding your edit, the last line is exactly what I'm asking about. I'll need to change my code to loop through the files instead of only the four directories. I have made an edit of my own in my original post that might do just that. I expect it will work (or some form of it, anyway), but I would appreciate another set of eyes!Flor
I have added some newer code to my original post. I now have it looping through each file, rather than the directories, and now I need to implement a SwingWorker so that I can run the methods in the background. I have a comment in my code (where I call the methods to create the backup directory and to copy the directories) showing what needs to be done. Unfortunately I am unfamiliar with the use of SwingWorkers, so I'm hoping someone would be willing to give me a hand with that part. Much obliged!Flor
S
2

Here is my idea using Java 8 and apache FileUtils (can be replaced by any other). It simply checks (in separate thread) directory size every n seconds:

FUNCTIONAL INTERFACE:

public interface DirectoryMovementStatusFeedback {
    void notifyStatus(double percentMoved, double speedInMB);
}

SERVICE:

public class FileMovementStatusUpdater {

    private long checkInterval = 2000;
    private boolean interrupted = false;

    public long getCheckInterval() {
        return checkInterval;
    }

    public void setCheckInterval(long checkInterval) {
        this.checkInterval = checkInterval;
    }

    public void monitor(File directory, long desiredSize, DirectoryMovementStatusFeedback feedback) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    double percentageMoved = 0;
                    double lastCheckedSize = 0;
                    do {
                        double currentSize = directory.exists() ? FileUtils.sizeOfDirectory(directory) : 0;
                        double speed = (currentSize - lastCheckedSize) / (1024 * checkInterval);
                        lastCheckedSize = currentSize;
                        percentageMoved = 100 * currentSize / desiredSize;
                        feedback.notifyStatus(percentageMoved, speed);
                        Thread.sleep(checkInterval);
                    } while (percentageMoved < 100 && !interrupted);
                } catch (Exception e) {
                    System.err.println("Directory monitor failed. " + e.getMessage());
                }
            }
        }).start();
    }

    public void stopMonitoring() {
        this.interrupted = true;
    }

USAGE:

    FileMovementStatusUpdater dirStatus = new FileMovementStatusUpdater();
    try {
        dirStatus.monitor(destination, FileUtils.sizeOfDirectory(source), (percent, speed) -> {
            progressBar.setValue(percent);
            speedLabel.setValue(speed+" MB/s");
        });
        FileUtils.moveDirectory(source, destination);
    } catch (Exception e) {
        // something
    }
    dirStatus.stopMonitoring();
Sakhuja answered 23/6, 2017 at 15:32 Comment(0)
K
1

This might be more complicated and maybe stupid idea bud,Maybe it helps so i decided to post it. Use your own method to copy file ,check file size ,take 1 percent from it and each time you reach amount of bites/mb/ that represent 1 percent of that file update your progress bar

Im short on time so i will at least paste some code with comments so you get idea of what i think.

//check size of files/dir to copy,before calling method bellow
//then you coud use for loop to do all the work

//make counter of how many mb/bits you already did.

    public void copyDirectory(File sourceLocation , File targetLocation) throws IOException {
        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }

            String[] children = sourceLocation.list();
            for (int i=0; i<children.length; i++) {
                copyDirectory(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]));
            }
        } else {

            InputStream in = new FileInputStream(sourceLocation);
            OutputStream out = new FileOutputStream(targetLocation);


            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
                //increment counter 
                //check if counter is above next 1% of size of your dirs

                //if you are then update progress bar by one percent
            }
            in.close();
            out.close();
        }
    }

This solution is not tested bud this is how i woud start to aproach this problem.

Kerseymere answered 24/12, 2013 at 23:2 Comment(2)
I know I told you I'd accept your answer, but it turns out it won't quite work for me after all. I gave you an upvote, but I'm about to post my own solution which seems to have worked. There are still a few glitches, but it works, and I wanted to let you guys, and future readers, know what I did. Many thanks for all your help though, you got me thinking outside of the box!Flor
Thank you men ,no problem i just wanted to help like we all do here.Glad that you found solution!Kerseymere

© 2022 - 2024 — McMap. All rights reserved.