hd3gtv.mydmam.useraction.fileoperation.CopyMove.java Source code

Java tutorial

Introduction

Here is the source code for hd3gtv.mydmam.useraction.fileoperation.CopyMove.java

Source

/*
 * This file is part of MyDMAM.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * Copyright (C) hdsdi3g for hd3g.tv 26 fvr. 2015
 * 
*/
package hd3gtv.mydmam.useraction.fileoperation;

import hd3gtv.log2.Log2;
import hd3gtv.log2.Log2Dump;
import hd3gtv.mydmam.manager.JobProgression;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.zip.CRC32;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

public class CopyMove {

    public enum FileExistsPolicy {
        OVERWRITE, IGNORE, RENAME
    }

    public static void checkExistsCanRead(File element) throws IOException, NullPointerException {
        if (element == null) {
            throw new NullPointerException("element is null");
        }
        if (element.exists() == false) {
            throw new FileNotFoundException("\"" + element.getPath() + "\" in filesytem");
        }
        if (element.canRead() == false) {
            throw new IOException("Can't read element \"" + element.getPath() + "\"");
        }
    }

    public static void checkIsDirectory(File element) throws FileNotFoundException {
        if (element.isDirectory() == false) {
            throw new FileNotFoundException("\"" + element.getPath() + "\" is not a directory");
        }
    }

    public static void checkIsWritable(File element) throws IOException {
        if (element.canWrite() == false) {
            throw new IOException("\"" + element.getPath() + "\" is not writable");
        }
    }

    private File source;
    private File destination;
    private boolean delete_after_copy;
    private JobProgression progression;

    private long total_size = 0;
    private long progress_copy = 0;
    private int actual_progress_value = 0;
    private int last_progress_value = 0;
    private int progress_size = 0;
    private FileExistsPolicy fileexistspolicy;

    public CopyMove(File source, File destination) throws NullPointerException, IOException {
        this.source = source;
        this.destination = destination;

        checkExistsCanRead(source);

        checkExistsCanRead(destination);
        checkIsDirectory(destination);
        checkIsWritable(destination);
        fileexistspolicy = FileExistsPolicy.OVERWRITE;
    }

    public CopyMove setFileExistsPolicy(FileExistsPolicy fileexistspolicy) {
        if (fileexistspolicy == null) {
            return this;
        }
        this.fileexistspolicy = fileexistspolicy;
        return this;
    }

    public CopyMove setDelete_after_copy(boolean delete_after_copy) throws IOException {
        this.delete_after_copy = delete_after_copy;
        if (delete_after_copy) {
            checkIsWritable(source);
        }
        return this;
    }

    private boolean force_move_with_copy_delete;

    private CopyMove forceMoveWithCopyDelete() throws IOException {
        setDelete_after_copy(true);
        force_move_with_copy_delete = true;
        return this;
    }

    public CopyMove setProgression(JobProgression progression) {
        this.progression = progression;
        return this;
    }

    public void operate() throws IOException {
        if (delete_after_copy & (force_move_with_copy_delete == false)) {
            /**
             * Move operation
             * Can we simply move ?
             */
            File moveto = new File(destination.getAbsoluteFile() + File.separator + source.getName());
            if (source.renameTo(moveto)) {
                return;
            } else {
                Log2Dump dump = new Log2Dump();
                dump.add("source", source);
                dump.add("moveto", moveto);
                Log2.log.debug("Can't simply move, do a copy + move operation", dump);
            }
        }

        ArrayList<File> list_to_copy = new ArrayList<File>();
        /**
         * Dirlist source
         */
        if (source.isDirectory()) {
            dirListing(source, list_to_copy);
        } else {
            list_to_copy.add(source);
        }

        File item_to_copy;
        /**
         * Compute total_size to display progress.
         */
        total_size = 0;
        for (int pos_ltc = 0; pos_ltc < list_to_copy.size(); pos_ltc++) {
            item_to_copy = list_to_copy.get(pos_ltc);
            total_size += item_to_copy.length();
        }

        /**
         * check UsableSpace
         */
        if (destination.getUsableSpace() > 0) {
            if (total_size > destination.getUsableSpace()) {
                throw new IOException("Not enough space in destination directory");
            }
        }

        /**
         * Copy source
         */
        File destination_element_to_copy;
        progress_size = ((int) total_size / (1024 * 1024));
        actual_progress_value = 0;
        last_progress_value = 0;
        progress_copy = 0;

        int chars_to_ignore = source.getParentFile().getAbsolutePath().length() + 1;
        String base_destination_path = destination.getAbsolutePath() + File.separator;

        for (int pos_ltc = 0; pos_ltc < list_to_copy.size(); pos_ltc++) {
            item_to_copy = list_to_copy.get(pos_ltc);
            destination_element_to_copy = new File(
                    base_destination_path + item_to_copy.getAbsolutePath().substring(chars_to_ignore));

            if (item_to_copy.isDirectory()) {
                if (destination_element_to_copy.exists()) {
                    if (destination_element_to_copy.isDirectory() == false) {
                        throw new IOException("Destination directory exists, and it not a directory: \""
                                + destination_element_to_copy.getPath() + "\"");
                    }
                } else {
                    if (destination_element_to_copy.mkdirs() == false) {
                        throw new IOException("Can't create destination directory: \""
                                + destination_element_to_copy.getPath() + "\"");
                    }
                }
            } else {
                copyFile(item_to_copy, destination_element_to_copy);
            }

            if (progression != null) {
                progress_copy += destination_element_to_copy.length();
                actual_progress_value = (int) (progress_copy / (1024 * 1024));
                if (actual_progress_value > last_progress_value) {
                    last_progress_value = actual_progress_value;
                    progression.updateProgress(actual_progress_value, progress_size);
                }
            }
        }

        if (delete_after_copy) {
            /**
             * To complete a move operation
             */
            for (int pos_ltc = list_to_copy.size() - 1; pos_ltc > -1; pos_ltc--) {
                item_to_copy = list_to_copy.get(pos_ltc);
                if (item_to_copy.delete() == false) {
                    throw new IOException("Can't delete moved file: \"" + item_to_copy.getPath() + "\"");
                }
            }
        }
    }

    /**
     * 50 Mbytes in bytes
     */
    private static final long FIFTY_MB = 52428800;

    private void copyFile(File source_file, File destination_file) throws IOException {
        if (destination_file.exists()) {
            Log2Dump dump = new Log2Dump();
            dump.add("source_file", source_file);
            dump.add("destination_file", destination_file);
            dump.add("delete_after_copy", delete_after_copy);

            if (fileexistspolicy == FileExistsPolicy.IGNORE) {
                Log2.log.debug("Destination file exists, ignore copy/move", dump);
                return;
            } else if (fileexistspolicy == FileExistsPolicy.OVERWRITE) {
                Log2.log.debug("Destination file exists, overwrite it", dump);
                FileUtils.forceDelete(destination_file);
            } else if (fileexistspolicy == FileExistsPolicy.RENAME) {
                // destination_file
                int cursor = 1;
                int dot_pos;
                StringBuilder sb;
                while (destination_file.exists()) {
                    sb = new StringBuilder();
                    sb.append(destination_file.getParent());
                    sb.append(File.separator);
                    dot_pos = destination_file.getName().lastIndexOf(".");
                    if (dot_pos > 0) {
                        sb.append(destination_file.getName().substring(0, dot_pos));
                        sb.append(" (");
                        sb.append(cursor);
                        sb.append(")");
                        sb.append(
                                destination_file.getName().substring(dot_pos, destination_file.getName().length()));
                    } else {
                        sb.append(destination_file.getName());
                        sb.append(" (");
                        sb.append(cursor);
                        sb.append(")");
                    }
                    destination_file = new File(sb.toString());
                    cursor++;
                }
                dump.add("new destination file name", destination_file);
                Log2.log.debug("Destination file exists, change destionation name", dump);
            }
        }
        /**
         * Imported from org.apache.commons.io.FileUtils
         * Licensed to the Apache Software Foundation,
         * http://www.apache.org/licenses/LICENSE-2.0
         */
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel input = null;
        FileChannel output = null;
        try {
            fis = new FileInputStream(source_file);
            fos = new FileOutputStream(destination_file);
            input = fis.getChannel();
            output = fos.getChannel();
            long size = input.size();
            long pos = 0;
            long count = 0;
            while (pos < size) {
                count = (size - pos) > FIFTY_MB ? FIFTY_MB : (size - pos);
                pos += output.transferFrom(input, pos, count);

                if (progression != null) {
                    actual_progress_value = (int) ((pos + progress_copy) / (1024 * 1024));
                    if (actual_progress_value > last_progress_value) {
                        last_progress_value = actual_progress_value;
                        progression.updateProgress(actual_progress_value, progress_size);
                    }
                }
            }
        } finally {
            IOUtils.closeQuietly(output);
            IOUtils.closeQuietly(fos);
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(fis);
        }

        if (source_file.length() != destination_file.length()) {
            throw new IOException(
                    "Failed to copy full contents from '" + source_file + "' to '" + destination_file + "'");
        }
        if (destination_file.setExecutable(source_file.canExecute()) == false) {
            Log2.log.error("Can't set Executable status to dest file", new IOException(destination_file.getPath()));
        }
        if (destination_file.setLastModified(source_file.lastModified()) == false) {
            Log2.log.error("Can't set LastModified status to dest file",
                    new IOException(destination_file.getPath()));
        }
    }

    private static void dirListing(File source, ArrayList<File> list_to_copy) throws IOException {
        ArrayList<File> bucket = new ArrayList<File>();
        ArrayList<File> next_bucket = new ArrayList<File>();
        bucket.add(source);

        File bucket_item;
        File[] list_content;

        while (true) {
            for (int pos_b = 0; pos_b < bucket.size(); pos_b++) {
                bucket_item = bucket.get(pos_b).getCanonicalFile();

                if (FileUtils.isSymlink(bucket_item)) {
                    continue;
                }

                if (list_to_copy.contains(bucket_item)) {
                    continue;
                }
                if (bucket_item.isDirectory()) {
                    list_content = bucket_item.listFiles();
                    for (int pos_lc = 0; pos_lc < list_content.length; pos_lc++) {
                        next_bucket.add(list_content[pos_lc]);
                    }
                }
                list_to_copy.add(bucket_item);
            }
            if (next_bucket.isEmpty()) {
                return;
            }
            bucket.clear();
            bucket.addAll(next_bucket);
            next_bucket.clear();
        }
    }

    private CopyMove() {
    }

    /**
     * Do a complete and recursive test for this class.
     * Test copy, move (rename) and move (copy + delete) for a file, and directories with a thousand of files inside.
     */
    private class InternalTests {
        Random random = new Random(System.nanoTime());
        CRC32 crc32 = new CRC32();

        private class TestFile {
            String relative_path;
            long size = 0;
            long cksum = 0;
            boolean directory = false;

            public TestFile(File root, File source) throws IOException {
                relative_path = source.getPath().substring(root.getPath().length());
                if (source.isDirectory()) {
                    directory = true;
                } else {
                    size = source.length();

                    crc32.reset();
                    cksum = FileUtils.checksum(source, crc32).getValue();
                }
            }

            void checkFile(File parent) throws IOException {
                File compare = new File(parent.getPath() + relative_path);

                checkExistsCanRead(compare);

                if (directory) {
                    checkIsDirectory(compare);
                    return;
                }

                if (size != compare.length()) {
                    throw new IOException("Invalid size with " + relative_path);
                }

                crc32.reset();
                long cksum_new = FileUtils.checksum(compare, crc32).getValue();
                if (cksum != cksum_new) {
                    throw new IOException(
                            "Invalid cksum with " + relative_path + " (" + cksum + "/" + cksum_new + ")");
                }
            }

            public String toString() {
                StringBuilder sb = new StringBuilder();
                sb.append(relative_path);
                sb.append("\t");
                sb.append(size);
                sb.append("\t");
                sb.append(cksum);
                sb.append("\t");
                sb.append(directory);
                return sb.toString();
            }
        }

        InternalTests(File temp_dir) throws Exception {
            checkExistsCanRead(temp_dir);
            checkIsDirectory(temp_dir);
            checkIsWritable(temp_dir);

            ArrayList<TestFile> test_files;
            CopyMove cm;
            File parent_source;
            File parent_destination;
            TestFile test_file;

            /**
             * Test copy file
             */
            parent_source = createTempFile(temp_dir, false);
            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            test_file = new TestFile(temp_dir, parent_source);
            test_file.checkFile(temp_dir);
            cm = new CopyMove(parent_source, parent_destination);
            cm.operate();
            test_file.checkFile(parent_destination);
            FileUtils.deleteQuietly(parent_source);
            FileUtils.deleteQuietly(parent_destination);

            /**
             * Test move (rename) file
             */
            parent_source = createTempFile(temp_dir, false);
            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            test_file = new TestFile(temp_dir, parent_source);
            test_file.checkFile(temp_dir);
            cm = new CopyMove(parent_source, parent_destination);
            cm.setDelete_after_copy(true);
            cm.operate();
            test_file.checkFile(parent_destination);
            if (parent_source.exists()) {
                throw new IOException("parent_source exists");
            }
            FileUtils.deleteQuietly(parent_source);
            FileUtils.deleteQuietly(parent_destination);

            /**
             * Test move (copy + delete) file
             */
            parent_source = createTempFile(temp_dir, false);
            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            test_file = new TestFile(temp_dir, parent_source);
            test_file.checkFile(temp_dir);
            cm = new CopyMove(parent_source, parent_destination);
            cm.forceMoveWithCopyDelete();
            cm.operate();
            test_file.checkFile(parent_destination);
            if (parent_source.exists()) {
                throw new IOException("parent_source exists");
            }
            FileUtils.deleteQuietly(parent_source);
            FileUtils.deleteQuietly(parent_destination);

            /**
             * Test copy directory
             */
            parent_source = prepareWorkingDirectory(temp_dir, "source");
            test_files = prepareTestFiles(parent_source);
            checkFiles(parent_source, test_files);

            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            cm = new CopyMove(parent_source, parent_destination);
            cm.operate();
            checkFiles(new File(parent_destination.getPath() + File.separator + parent_source.getName()),
                    test_files);
            FileUtils.deleteQuietly(parent_destination);

            /**
             * Test move (rename) directory
             */
            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            cm = new CopyMove(parent_source, parent_destination);
            cm.setDelete_after_copy(true);
            cm.operate();
            checkFiles(new File(parent_destination.getPath() + File.separator + parent_source.getName()),
                    test_files);
            if (parent_source.exists()) {
                throw new IOException("can't move parent source " + parent_source.getPath());
            }
            FileUtils.deleteQuietly(parent_destination);

            /**
             * Test move (copy + delete) directory
             */
            parent_source = prepareWorkingDirectory(temp_dir, "source");
            test_files = prepareTestFiles(parent_source);
            checkFiles(parent_source, test_files);
            parent_destination = prepareWorkingDirectory(temp_dir, "dest");
            cm = new CopyMove(parent_source, parent_destination);
            cm.forceMoveWithCopyDelete();
            cm.operate();
            checkFiles(new File(parent_destination.getPath() + File.separator + parent_source.getName()),
                    test_files);
            if (parent_source.exists()) {
                throw new IOException("can't move parent source " + parent_source.getPath());
            }
            FileUtils.deleteQuietly(parent_destination);

            FileUtils.deleteQuietly(parent_source);
        }

        File prepareWorkingDirectory(File root, String name) throws IOException {
            File item_dir = new File(root.getPath() + File.separator + name);

            if (item_dir.exists()) {
                FileUtils.deleteQuietly(item_dir);
            }
            if (item_dir.mkdir() == false) {
                throw new IOException("Can't create source file \"" + item_dir + "\"");
            }
            return item_dir;
        }

        void checkFiles(File parent, ArrayList<TestFile> test_files) throws IOException {
            for (int pos = 0; pos < test_files.size(); pos++) {
                test_files.get(pos).checkFile(parent);
            }
        }

        ArrayList<TestFile> prepareTestFiles(File parent) throws IOException {

            List<File> dirs = createTreeDir(parent);
            List<File> files = new ArrayList<File>(dirs.size());

            /**
             * Add a 50MB file
             */
            files.add(createTempFile(dirs.get(random.nextInt(dirs.size())), true));

            /**
             * Add a lot of small files.
             */
            for (int pos_d = 0; pos_d < dirs.size(); pos_d++) {
                for (int pos_f = 0; pos_f < random.nextInt(10) - 1; pos_f++) {
                    files.add(createTempFile(dirs.get(pos_d), false));
                }
            }

            ArrayList<TestFile> test_files = new ArrayList<CopyMove.InternalTests.TestFile>(
                    dirs.size() + files.size());

            for (int pos = 0; pos < dirs.size(); pos++) {
                test_files.add(new TestFile(parent, dirs.get(pos)));
            }
            for (int pos = 0; pos < files.size(); pos++) {
                test_files.add(new TestFile(parent, files.get(pos)));
            }
            return test_files;
        }

        String createTempName(boolean isfile) {
            StringBuilder sb = new StringBuilder();

            if (random.nextInt(10) == 1) {
                sb.append(".");
            }

            int val;
            for (int pos = 0; pos < (random.nextInt(20) + 1); pos++) {
                val = random.nextInt(91 - 65);
                sb.append(Character.toChars(val + 65));
            }
            sb.append(" ");
            sb.append(random.nextInt(10000));

            if (isfile) {
                sb.append(".");
                for (int pos = 0; pos < 3; pos++) {
                    val = random.nextInt(122 - 97);
                    sb.append(Character.toChars(val + 97));
                }
            }

            return sb.toString();
        }

        List<File> createTreeDir(File parent) throws IOException {
            ArrayList<File> result = new ArrayList<File>();

            for (int i = 0; i < 5; i++) {
                result.add(createTempDir(parent));
            }

            ArrayList<File> temp = new ArrayList<File>();
            for (int d = 0; d < 5; d++) {
                temp.clear();
                temp.addAll(result);
                for (int pos = 0; pos < temp.size(); pos++) {
                    for (int i = 0; i < random.nextInt(5); i++) {
                        result.add(createTempDir(temp.get(pos)));
                    }
                }
            }
            return result;
        }

        File createTempDir(File parent) throws IOException {
            File new_dir = new File(parent.getPath() + File.separator + createTempName(false));
            if (new_dir.mkdir() == false) {
                throw new IOException("Can't create temp dir");
            }
            return new_dir;
        }

        File createTempFile(File parent, boolean big_file) throws IOException {
            File new_file = new File(parent.getPath() + File.separator + createTempName(true));

            FileOutputStream fos = new FileOutputStream(new_file);
            int size = random.nextInt(500);
            byte[] buffer = new byte[random.nextInt(500)];
            for (int pos = 0; pos < size; pos++) {
                random.nextBytes(buffer);
                fos.write(buffer, 0, buffer.length);
            }
            if (big_file) {
                size = size * 10;
                buffer = new byte[random.nextInt(10000)];
                while (new_file.length() < (FIFTY_MB * 1.5)) {
                    fos.flush();
                    for (int pos = 0; pos < size; pos++) {
                        random.nextBytes(buffer);
                        fos.write(buffer, 0, buffer.length);
                    }
                }
            }
            fos.close();

            return new_file;
        }
    }

    /**
     * @param args with a empty dir
     */
    public static void main(String[] args) throws Exception {
        new CopyMove().new InternalTests(new File(args[0]).getCanonicalFile());
    }

}