4

I have the below code to copy directories and files but not sure where to measure the progress. Can someone help
as to where can I measure how much has been copied and show it in the JProgress bar

public static void copy(File src, File dest)
throws IOException{
if(src.isDirectory()){
        if(!dest.exists()){ //checking whether destination directory exisits
           dest.mkdir();
           System.out.println("Directory copied from " 
                        + src + "  to " + dest);
        }

        String files[] = src.list();

        for (String file : files) {

           File srcFile = new File(src, file);
           File destFile = new File(dest, file);

           copyFolder(srcFile,destFile);
        }
  }else{
        InputStream in = new FileInputStream(src);
          OutputStream out = new FileOutputStream(dest); 

          byte[] buffer = new byte[1024];

          int length;

          while ((length = in.read(buffer)) > 0){
           out.write(buffer, 0, length);
          }

          in.close();
          out.close();
          System.out.println("File copied from " + src + " to " + dest);
  }
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
user1815823
  • 617
  • 2
  • 8
  • 14
  • Not sure, but this may help you: http://code.google.com/p/nativelibs4java/ OR http://www.strixcode.com/j7goodies/ – djangofan Nov 26 '12 at 23:11

3 Answers3

14

I have just written this utility for you :) (It takes me about 3 hours):

enter image description here

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultCaret;

public class FileCopierUtility extends JFrame implements ActionListener, PropertyChangeListener
{
    private static final long serialVersionUID = 1L;

    private JTextField txtSource;
    private JTextField txtTarget;
    private JProgressBar progressAll;
    private JProgressBar progressCurrent;
    private JTextArea txtDetails;
    private JButton btnCopy;
    private CopyTask task;

    public FileCopierUtility()
    {
        buildGUI();
    }

    private void buildGUI()
    {
        setTitle("File Copier Utility");
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                if(task != null) task.cancel(true);
                dispose();
                System.exit(0);
            }
        });

        JLabel lblSource = new JLabel("Source Path: ");
        JLabel lblTarget = new JLabel("Target Path: ");
        txtSource = new JTextField(50);
        txtTarget = new JTextField(50);
        JLabel lblProgressAll = new JLabel("Overall: ");
        JLabel lblProgressCurrent = new JLabel("Current File: ");
        progressAll = new JProgressBar(0, 100);
        progressAll.setStringPainted(true);
        progressCurrent = new JProgressBar(0, 100);
        progressCurrent.setStringPainted(true);
        txtDetails = new JTextArea(5, 50);
        txtDetails.setEditable(false);
        DefaultCaret caret = (DefaultCaret) txtDetails.getCaret();
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
        JScrollPane scrollPane = new JScrollPane(txtDetails, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        btnCopy = new JButton("Copy");
        btnCopy.setFocusPainted(false);
        btnCopy.setEnabled(false);
        btnCopy.addActionListener(this);

        DocumentListener listener = new DocumentListener()
        {
            @Override
            public void removeUpdate(DocumentEvent e)
            {
                boolean bEnabled = txtSource.getText().length() > 0 && txtTarget.getText().length() > 0;
                btnCopy.setEnabled(bEnabled);
            }

            @Override
            public void insertUpdate(DocumentEvent e)
            {
                boolean bEnabled = txtSource.getText().length() > 0 && txtTarget.getText().length() > 0;
                btnCopy.setEnabled(bEnabled);
            }

            @Override
            public void changedUpdate(DocumentEvent e){}
        };

        txtSource.getDocument().addDocumentListener(listener);
        txtTarget.getDocument().addDocumentListener(listener);

        JPanel contentPane = (JPanel) getContentPane();
        contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        JPanel panInputLabels = new JPanel(new BorderLayout(0, 5));
        JPanel panInputFields = new JPanel(new BorderLayout(0, 5));
        JPanel panProgressLabels = new JPanel(new BorderLayout(0, 5));
        JPanel panProgressBars = new JPanel(new BorderLayout(0, 5));

        panInputLabels.add(lblSource, BorderLayout.NORTH);
        panInputLabels.add(lblTarget, BorderLayout.CENTER);
        panInputFields.add(txtSource, BorderLayout.NORTH);
        panInputFields.add(txtTarget, BorderLayout.CENTER);
        panProgressLabels.add(lblProgressAll, BorderLayout.NORTH);
        panProgressLabels.add(lblProgressCurrent, BorderLayout.CENTER);
        panProgressBars.add(progressAll, BorderLayout.NORTH);
        panProgressBars.add(progressCurrent, BorderLayout.CENTER);

        JPanel panInput = new JPanel(new BorderLayout(0, 5));
        panInput.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Input"), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
        JPanel panProgress = new JPanel(new BorderLayout(0, 5));
        panProgress.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Progress"), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
        JPanel panDetails = new JPanel(new BorderLayout());
        panDetails.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Details"), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
        JPanel panControls = new JPanel(new BorderLayout());
        panControls.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));

        panInput.add(panInputLabels, BorderLayout.LINE_START);
        panInput.add(panInputFields, BorderLayout.CENTER);
        panProgress.add(panProgressLabels, BorderLayout.LINE_START);
        panProgress.add(panProgressBars, BorderLayout.CENTER);
        panDetails.add(scrollPane, BorderLayout.CENTER);
        panControls.add(btnCopy, BorderLayout.CENTER);

        JPanel panUpper = new JPanel(new BorderLayout());
        panUpper.add(panInput, BorderLayout.NORTH);
        panUpper.add(panProgress, BorderLayout.SOUTH);

        contentPane.add(panUpper, BorderLayout.NORTH);
        contentPane.add(panDetails, BorderLayout.CENTER);
        contentPane.add(panControls, BorderLayout.SOUTH);

        pack();
        setLocationRelativeTo(null);
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        if("Copy".equals(btnCopy.getText()))
        {
            File source = new File(txtSource.getText());
            File target = new File(txtTarget.getText());

            if(!source.exists())
            {
                JOptionPane.showMessageDialog(this, "The source file/directory does not exist!", "ERROR", JOptionPane.ERROR_MESSAGE);
                return;
            }

            if(!target.exists() && source.isDirectory()) target.mkdirs();
            else
            {
                int option = JOptionPane.showConfirmDialog(this, "The target file/directory already exists, do you want to overwrite it?", "Overwrite the target", JOptionPane.YES_NO_OPTION);
                if(option != JOptionPane.YES_OPTION) return;
            }

            task = this.new CopyTask(source, target);
            task.addPropertyChangeListener(this);
            task.execute();

            btnCopy.setText("Cancel");
        }
        else if("Cancel".equals(btnCopy.getText()))
        {
            task.cancel(true);
            btnCopy.setText("Copy");
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        if("progress".equals(evt.getPropertyName()))
        {
            int progress = (Integer) evt.getNewValue();
            progressAll.setValue(progress);
        }
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {   
            @Override
            public void run()
            {
                new FileCopierUtility().setVisible(true);
            }
        });
    }

    class CopyTask extends SwingWorker<Void, Integer>
    {
        private File source;
        private File target;
        private long totalBytes = 0L;
        private long copiedBytes = 0L;

        public CopyTask(File source, File target)
        {
            this.source = source;
            this.target = target;

            progressAll.setValue(0);
            progressCurrent.setValue(0);
        }

        @Override
        public Void doInBackground() throws Exception
        {
            txtDetails.append("Retrieving some info ... ");
            retrieveTotalBytes(source);
            txtDetails.append("Done!\n");

            copyFiles(source, target);
            return null;
        }

        @Override
        public void process(List<Integer> chunks)
        {
            for(int i : chunks)
            {
                progressCurrent.setValue(i);
            }
        }

        @Override
        public void done()
        {
            setProgress(100);
            btnCopy.setText("Copy");
        }

        private void retrieveTotalBytes(File sourceFile)
        {
            File[] files = sourceFile.listFiles();
            for(File file : files)
            {
                if(file.isDirectory()) retrieveTotalBytes(file);
                else totalBytes += file.length();
            }
        }

        private void copyFiles(File sourceFile, File targetFile) throws IOException
        {
            if(sourceFile.isDirectory())
            {
                if(!targetFile.exists()) targetFile.mkdirs();

                String[] filePaths = sourceFile.list();

                for(String filePath : filePaths)
                {
                    File srcFile = new File(sourceFile, filePath);
                    File destFile = new File(targetFile, filePath);

                    copyFiles(srcFile, destFile);
                }
            }
            else
            {
                txtDetails.append("Copying " + sourceFile.getAbsolutePath() + " ... ");

                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile));

                long fileBytes = sourceFile.length();
                long soFar = 0L;

                int theByte;

                while((theByte = bis.read()) != -1)
                {
                    bos.write(theByte);

                    setProgress((int) (copiedBytes++ * 100 / totalBytes));
                    publish((int) (soFar++ * 100 / fileBytes));
                }

                bis.close();
                bos.close();

                publish(100);

                txtDetails.append("Done!\n");
            }
        }
    }
}
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • There is virtual no benifit to be gained from reading/writing a single byte on each loop through a buffered stream - IMHO, you will generally get better performance from using a byte array – MadProgrammer Nov 27 '12 at 02:17
  • @MadProgrammer AFAIK, my code is behaving exactly as yours, filling the buffer and write it to the stream. The only difference is that `BufferedStream` classes handle it instead of filling the buffer myself. Beside that, the buffer size could be different. – Eng.Fouad Nov 27 '12 at 02:38
  • (It wasn't my code, I'd use a `BufferedReader/Writer` personally - I stole the code from the OP ;)) I did a quick test, using `BufferedInput/outStream` with a single byte value and byte array of 1mg and using a 8 meg file, across the same disk it was negotiable, across the network, using the byte array saved 2 seconds, using plain streams (and byte array) it was 100th a second slower then using the byte array and buffered stream. – MadProgrammer Nov 27 '12 at 02:44
  • Awesome Eng.Fouad. This is what I was looking for. Thanks a Million – user1815823 Nov 27 '12 at 03:42
4

To accomplish this, you have to figure out the number of copy operations in advance. You did not include your copyFolder method, but I assume that you would like to perform a recursive copy. If so, you have to traverse the whole directory tree to figure out how many files you are going to copy. This can get nasty for the user when dealing with large directory structures. (This is why modern operation systems often display an annoying "preparing to copy..." - message before the operation starts and progress is displayed)

mbelow
  • 1,093
  • 6
  • 11
4

Showing the progress for a file is rather simple...

long expectedBytes = src.length(); // This is the number of bytes we expected to copy..
byte[] buffer = new byte[1024];
int length;
long totalBytesCopied = 0; // This will track the total number of bytes we've copied
while ((length = in.read(buffer)) > 0){
    out.write(buffer, 0, length);
    totalBytesCopied += length;
    int progress = (int)Math.round(((double)totalBytesCopied / (double)expectedBytes) * 100);
    setProgress(progress); // From SwingWorker
}

If you want to provide information about the total number of bytes to be copied/has begin copied, it becomes a little more complicated.

You have choices here. You either pre-scan all the folders (I'd be placing the files into some kind of queue) and calculate the total bytes/number of files to be copied. Then start the actual copy process.

Updating the progress for this is as simple as the example before, except you'd be accumulating the total number of bytes you've copied over the whole process instead of a single file.

OR...

You could use a producer/consumer algorithm. The idea would be to start a thread/worker whose sole responsibility was to scan the folders for the files to be copied and place them into a central/shared queue (this is the producer).

You would have a second thread/worker that would pop the next item of this queue (blocking into a new file became available) and would actually copy the file.

The trick here is for the queue to maintain a counter to the total number of files and bytes that has passed through it, this will allow you to adjust the ongoing progress of total job...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • +1 For details. BTW, shouldn't it be easier to use `BufferedStream`s rather than using plain streams? – Eng.Fouad Nov 27 '12 at 02:07
  • @Eng.Fouad Easier? Probably not, still the same style of code, more efficient? that would depend on a number of factors. A good idea? I'd encourage it. – MadProgrammer Nov 27 '12 at 02:15