Прогресс-бар при копировании файлов с Java

Я уверен, что этот вопрос задавался раньше, но ни один из ответов, которые я нашел, не будет работать очень хорошо с моим существующим кодом. Я отправляю этот вопрос, если есть способ сделать это, не полностью переделывая то, что у меня есть до сих пор.

идея в том, чтобы показать очень простой прогресс-бар при копировании файлов и папок с одного диска на другой. У меня есть класс BasicCopy, который предназначен для копирования содержимого папок Pictures, Documents, videos и Music (стандарт на машинах Windows) для папок с одинаковыми именами в каталоге резервного копирования на втором диске. Вот класс до сих пор:

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);
    }
}

у меня есть метод в предыдущем классе, который измеряет общий размер каталогов, поэтому я мог бы передать это в этот класс, если необходимо. Однако в настоящее время я просматриваю только четыре каталога, поэтому я ожидаю, что не смогу увеличить индикатор выполнения с более высоким разрешением, чем 25% за тик. Мне интересно, есть ли способ ... можно ли изменить его, чтобы я мог включить индикатор выполнения для его мониторинга и сделать его немного более точным? Кроме того, я не уверен, следует ли это спрашивать в другом потоке или нет, но этот метод копирования файлов занимает очень много времени. Для копирования файлов на 500 МБ требуется несколько часов, и мне было интересно, может ли быть способ ускорить его? Эта часть не является приоритетом. Сейчас я в основном заинтересован в добавлении индикатора прогресса. Ура!

EDIT:

после некоторой возни я понял, что, вероятно, мог бы использовать код, подобный этому (этот точный код может работать или не работать-я просто быстро записал его, чтобы не забыть его, он все еще непроверен). Это позволит мне обновить индикатор выполнения для каждого скопированного файла.

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();
        }
    }
}

редактировать #2:

Я немного больше работал над кодом, и я считаю, что я понял большую часть этого. Теперь вопрос о свинг-работнике, который, я думаю, мне понадобится, чтобы запустить долгосрочные методы в фоновом режиме. В противном случае GUI становится невосприимчивым (много документации об этом в документах Java). Однако здесь я застрял. Я использовал SwingWorker только один раз, и это было в основном с скопированным кодом. Мне интересно, как я мог бы реализовать это, используя следующий код, чтобы индикатор выполнения (и остальная часть фрейма) действительно появился.

обновленный код:

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);
    }
}

4 ответов


Я нашел решение, которое работает. Он все еще имеет некоторые незначительные сбои, но в основном с незначительными деталями. Вот что у меня есть сейчас, и, кажется, это работает.

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();
    }
}

этот класс содержится в основном классе и запускается с использованием следующего кода:

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

это вызывается сразу после создания кадра и установки visible.

теперь, как я уже упоминал, это все еще не идеально. Например, проверьте, является ли это файл или каталог перед копированием его следует заменить рекурсивной функцией, чтобы он проходил через каждый отдельный файл, а не просто копировал каталог, если это полный каталог. Это позволит более точно обновлять индикатор выполнения и показывать отдельные файлы, копируемые в текстовой области, а не файлы и каталоги.

в любом случае, я просто хотел опубликовать этот код, чтобы показать, что сработало для меня, не полностью переделывая то, что у меня было. Я буду продолжать работать. это, хотя, и размещать любые важные обновления, которые могут помочь будущим читателям.

спасибо всем за ответы, они очень помогли!


Я предполагаю, что вам нужен графический индикатор выполнения, а не консоль (tui) решение. Вы читали этот свинг учебник?

EDIT: если вы хотите один тик вашего индикатора выполнения на файл, вам просто нужно сказать конструктору индикатора выполнения, сколько всего тиков, что-то вроде:

progressBar = new JProgressBar(0, allFilesInAllDirectoriesLength);

и организуйте цикл for для работы с каждым файлом, вместо того, чтобы зацикливаться на каталогах.


Это может быть сложнее и,возможно, глупая идея, может быть, это помогает, поэтому я решил опубликовать ее. Используйте свой собственный метод для копирования файла, проверьте размер файла, возьмите 1 процент от него, и каждый раз, когда вы достигаете количества укусов/Мб/, которые представляют 1 процент этого файла, обновите индикатор выполнения

Im короткое время, поэтому я, по крайней мере, вставлю код с комментариями, чтобы вы поняли, что я думаю.

//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();
        }
    }

это решение не тестируется bud вот как я начинаю подход к этой проблеме.


вот моя идея с использованием Java 8 и Апач пакета fileutils (может быть заменен любым другим). Он просто проверяет (в отдельном потоке) размер каталога каждый n секунд:

ФУНКЦИОНАЛЬНЫЙ ИНТЕРФЕЙС:

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

сервис:

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;
    }

использование:

    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();