Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Progress bar while copying files with Java

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);
    }
}
like image 959
DerStrom8 Avatar asked Dec 24 '13 22:12

DerStrom8


People also ask

How do you copy a file in Java?

Another common way to copy a file with Java is by using the commons-io library. The latest version can be downloaded from Maven Central. Then, to copy a file we just need to use the copyFile() method defined in the FileUtils class. The method takes a source and a target file.


4 Answers

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!

like image 92
DerStrom8 Avatar answered Oct 29 '22 06:10

DerStrom8


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.

like image 26
Amir Afghani Avatar answered Oct 29 '22 06:10

Amir Afghani


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();
like image 2
Damiano Avatar answered Oct 29 '22 04:10

Damiano


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.

like image 1
Tomas Bisciak Avatar answered Oct 29 '22 05:10

Tomas Bisciak