Package org.jnode.command.file

Source Code of org.jnode.command.file.CpCommand

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library 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 2.1 of the License, or
* (at your option) any later version.
*
* This library 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.file;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;

/**
* File copy utility.  This utility copies one file to another file, or multiple files or directories
* into an existing directory.  Files are copied byte-wise (not character-wise).  Recursive directory
* copy is supported.
*
* @author crawley@jnode.org
*/
public class CpCommand extends AbstractCommand {

    private static final String HELP_SOURCE = "source files or directories";
    private static final String HELP_TARGET = "target file or directory";
    private static final String HELP_FORCE = "if set, force overwrite of existing files";
    private static final String HELP_INTERACTIVE = "if set, ask before overwriting existing files";
    private static final String HELP_UPDATE = "if set, overwrite existing files older than their source";
    private static final String HELP_RECURSE = "if set, recursively copy source directories";
    private static final String HELP_VERBOSE = "if set, output a line for each file copied";
    private static final String HELP_SUPER = "Copy files and directories";
    private static final String ERR_NO_SOURCE = "No source files or directories supplied";
    private static final String ERR_NO_WRITE = "Target directory is not writable";
    private static final String ERR_MULTI_DIR = "Multi-file copy requires the target to be a directory";
    private static final String ERR_COPY_DIR_FILE = "Cannot copy a directory to a file";
    private static final String ERR_COPY_DEV = "Cannot copy to a device";
    private static final String FMT_VERBOSE_COPY = "File copied: %d, directories created: %d%n";
    private static final String ERR_MUTEX_FLAGS = "The force, interactive and update flags are mutually exclusive";
    private static final String FMT_NO_WRITE = "directory '%s' is not writeable";
    private static final String FMT_DIR_CREATE = "Creating directory '%s'%n";
    private static final String FMT_DIR_REPLACE = "Replacing file '%s' with a directory%n";
    private static final String FMT_DIR_SKIP = "not overwriting '%s' with a directory";
    private static final String FMT_IS_DIR = "'%s' is a directory";
    private static final String FMT_COPY_FILE = "Copying file '%s' as '%s'%n";
    private static final String FMT_SRC_NOEXIST = "'%s' does not exist";
    private static final String FMT_SRC_NOREAD = "'%s' cannot be read";
    private static final String FMT_SRC_DEVICE = "'%s' is a device";
    private static final String FMT_COPY_DIR_SELF = "Cannot copy directory '%s' into itself";
    private static final String FMT_COPY_FILE_SELF = "Cannot copy file '%s' to itself";
    private static final String FMT_COPY_SUB = "Cannot copy directory '%s' into a subdirectory ('%s')";
    private static final String FMT_NO_COPY_DIR = "Cannot copy '%s' onto directory '%s'";
    private static final String FMT_NO_COPY_DEV = "Cannot copy '%s' to device '%s'";
    private static final String FMT_EXISTS = "'%s' already exists";
    private static final String FMT_NEWER = "'%s' is newer than '%s'";
    private static final String FMT_ASK_OVERWRITE = "Overwrite '%s' with '%s'? [y/n]%n";
    private static final String ERR_COPY_EOF = "EOF - abandoning copying";
    private static final String ERR_COPY_IOEX = "IO Error - abandoning copying";
    private static final String STR_ASK_AGAIN = "Answer 'y' or 'n'";
    private static final String FMT_SKIP = "%s: skipping%n";
   
    static final byte MODE_NORMAL = 0;
    static final byte MODE_INTERACTIVE = 1;
    static final byte MODE_FORCE = 2;
    static final byte MODE_UPDATE = 3;

    private final FileArgument argSource;
    private final FileArgument argTarget;
    private final FlagArgument argForce;
    private final FlagArgument argInteractive;
    private final FlagArgument argUpdate;
    private final FlagArgument argRecursive;
    private final FlagArgument argVerbose;

    private byte mode = MODE_NORMAL;
    private boolean recursive = false;
    private boolean verbose = false;
    private int filesCopied = 0;
    private int directoriesCreated = 0;
    private BufferedReader in;
    private PrintWriter out;
    private PrintWriter err;
    private byte[] buffer = new byte[1024 * 8];

    public CpCommand() {
        super(HELP_SUPER);
        argSource = new FileArgument("source", Argument.MANDATORY | Argument.MULTIPLE | Argument.EXISTING, HELP_SOURCE);
        argTarget      = new FileArgument("target", Argument.MANDATORY, HELP_TARGET);
        argForce       = new FlagArgument("force", Argument.OPTIONAL, HELP_FORCE);
        argInteractive = new FlagArgument("interactive", Argument.OPTIONAL, HELP_INTERACTIVE);
        argUpdate      = new FlagArgument("update", Argument.OPTIONAL, HELP_UPDATE);
        argRecursive   = new FlagArgument("recursive", Argument.OPTIONAL, HELP_RECURSE);
        argVerbose     = new FlagArgument("verbose", Argument.OPTIONAL, HELP_VERBOSE);
        registerArguments(argSource, argTarget, argForce, argInteractive, argRecursive, argUpdate, argVerbose);
    }
   
    public static void main(String[] args) throws Exception {
        new CpCommand().execute(args);
    }

    public void execute() throws Exception {
        this.out = getOutput().getPrintWriter();
        this.err = getError().getPrintWriter();
        processFlags();
        if (mode == MODE_INTERACTIVE) {
            this.in = new BufferedReader(getInput().getReader());
        }
        File[] sources = argSource.getValues();
        File target = argTarget.getValue();
        if (sources.length == 0) {
            error(ERR_NO_SOURCE);
        }
        if (target.isDirectory()) {
            if (!target.canWrite()) {
                error(ERR_NO_WRITE);
            }
            for (File source : sources) {
                if (checkSafe(source, target)) {
                    copyIntoDirectory(source, target);
                }
            }
        } else if (sources.length > 1) {
            error(ERR_MULTI_DIR);
        } else {
            File source = sources[0];
            if (source.isDirectory()) {
                error(ERR_COPY_DIR_FILE);
            } else if (target.exists() && !target.isFile()) {
                error(ERR_COPY_DEV);
            } else {
                if (checkSafe(source, target)) {
                    copyToFile(source, target);
                }
            }
        }
        if (verbose) {
            out.format(FMT_VERBOSE_COPY, filesCopied, directoriesCreated);
        }
    }
   
    private void processFlags() {
        recursive = argRecursive.isSet();
        verbose = argVerbose.isSet();
        // The mode flags are mutually exclusive ...
        if (argForce.isSet()) {
            mode = MODE_FORCE;
        }
        if (argInteractive.isSet()) {
            if (mode != MODE_NORMAL) {
                error(ERR_MUTEX_FLAGS);
            }
            mode = MODE_INTERACTIVE;
        }
        if (argUpdate.isSet()) {
            if (mode != MODE_NORMAL) {
                error(ERR_MUTEX_FLAGS);
            }
            mode = MODE_UPDATE;
        }
    }
   
    /**
     * Copy a file or directory into a supplied target directory.
     *
     * @param source the name of the object to be copied
     * @param targetDir the destination directory
     * @throws IOException
     */
    private void copyIntoDirectory(File source, File targetDir) throws IOException {
        if (!targetDir.canWrite()) {
            skip(String.format(FMT_NO_WRITE, targetDir));
        } else if (source.isDirectory()) {
            if (recursive) {
                File newDir = new File(targetDir, source.getName());
                if (!newDir.exists()) {
                    if (verbose) {
                        out.format(FMT_DIR_CREATE, newDir);
                    }
                    newDir.mkdir();
                    directoriesCreated++;
                } else if (!newDir.isDirectory()) {
                    if (mode == MODE_FORCE) {
                        if (verbose) {
                            out.format(FMT_DIR_REPLACE, newDir);
                        }
                        newDir.delete();
                        newDir.mkdir();
                        directoriesCreated++;
                    } else {
                        skip(String.format(FMT_DIR_SKIP, newDir));
                        return;
                    }
                }
                String[] contents = source.list();
                for (String name : contents) {
                    if (name.equals(".") || name.equals("..")) {
                        continue;
                    }
                    copyIntoDirectory(new File(source, name), newDir);
                }
            } else {
                skip(String.format(FMT_IS_DIR, source));
            }
        } else {
            File newFile = new File(targetDir, source.getName());
            copyToFile(source, newFile);
        }
    }
   
    /**
     * Copy a file to (as) a file
     *
     * @param sourceFile
     * @param targetFile
     * @throws IOException
     */
    private void copyToFile(File sourceFile, File targetFile) throws IOException {
        if (!checkSafe(sourceFile, targetFile) ||
                !checkSource(sourceFile) ||
                !checkTarget(targetFile, sourceFile)) {
            return;
        }
        if (verbose) {
            out.format(FMT_COPY_FILE, sourceFile, targetFile);
        }
       
        InputStream sin = null;
        OutputStream tout = null;
        try {
            sin = new FileInputStream(sourceFile);
            tout = new FileOutputStream(targetFile);
            while (true) {
                int nosBytesRead = sin.read(buffer);
                if (nosBytesRead <= 0) {
                    break;
                }
                tout.write(buffer, 0, nosBytesRead);
            }
        } finally {
            if (sin != null) {
                try {
                    sin.close();
                } catch (IOException ex) {
                    // ignore
                }
            }
            if (tout != null) {
                try {
                    tout.close();
                } catch (IOException ex) {
                    // ignore
                }
            }
        }
        filesCopied++;
    }

    /**
     * Check that a source object exists, is readable and is either
     * a file or a directory.
     *
     * @param source
     * @return
     */
    private boolean checkSource(File source) {
        if (!source.exists()) {
            return skip(String.format(FMT_SRC_NOEXIST, source));
        } else if (!source.canRead()) {
            return skip(String.format(FMT_SRC_NOREAD, source));
        } else if (!(source.isFile() || source.isDirectory())) {
            return vskip(String.format(FMT_SRC_DEVICE, source));
        } else {
            return true;
        }
    }

    /**
     * Check that a copy is going to be safe.  Unsafe things are copying a
     * file to itself and copying a directory into itself or a subdirectory.
     *
     * @param source
     * @param target
     * @return
     * @throws IOException
     */
    private boolean checkSafe(File source, File target) throws IOException {
        // These checks must be done with canonical paths.  But fortunately they
        // don't need to be repeated for every file/directory in a recursive copy.
        String sourcePath = source.getCanonicalPath();
        String targetPath = target.getCanonicalPath();
        if (target.isDirectory()) {
            if (recursive && source.isDirectory()) {
                if (sourcePath.equals(targetPath)) {
                    return skip(String.format(FMT_COPY_DIR_SELF, source));
                }
                if (!sourcePath.endsWith(File.separator)) {
                    sourcePath = sourcePath + File.separatorChar;
                }
                if (targetPath.startsWith(sourcePath)) {
                    return skip(String.format(FMT_COPY_SUB, source, target));
                }
            }
        } else {
            if (sourcePath.equals(targetPath)) {
                return skip(String.format(FMT_COPY_FILE_SELF, source));
            }
        }
        return true;
    }

    /**
     * Check that the target can be written / overwritten.  In interactive mode,
     * the user is asked about clobbering existing files.  In update mode, they
     * are overwritten if the source is newer than the target.  In force mode, they
     * are clobbered without asking.  In normal mode, existing target files are
     * skipped.
     *
     * @param target
     * @param source
     * @return
     */
    private boolean checkTarget(File target, File source) {
        if (!target.exists()) {
            return true;
        }
        if (target.isDirectory() && !source.isDirectory()) {
            return skip(String.format(FMT_NO_COPY_DIR, source, target));
        }
        if (!target.isFile()) {
            return vskip(String.format(FMT_NO_COPY_DEV, source, target));
        }
        switch (mode) {
            case MODE_NORMAL:
                return vskip(String.format(FMT_EXISTS, target));
            case MODE_FORCE:
                return true;
            case MODE_UPDATE:
                return (source.lastModified() > target.lastModified() ||
                        vskip(String.format(FMT_NEWER, target, source)));
            case MODE_INTERACTIVE:
                out.format(FMT_ASK_OVERWRITE, target, source);
                while (true) {
                    try {
                        String line = in.readLine();
                        if (line == null) {
                            error(ERR_COPY_EOF);
                        }
                        if (line.length() > 0) {
                            if (line.charAt(0) == 'y' || line.charAt(0) == 'Y') {
                                return true;
                            } else if (line.charAt(0) == 'n' || line.charAt(0) == 'N') {
                                return vskip("'" + target + '\'');
                            }
                        }
                        out.print(STR_ASK_AGAIN);
                    } catch (IOException ex) {
                        error(ERR_COPY_IOEX);
                    }
                }
        }
        return false;
    }

    private void error(String msg) {
        err.println(msg);
        exit(1);
    }
   
    private boolean skip(String msg) {
        err.format(FMT_SKIP, msg);
        return false;
    }
   
    private boolean vskip(String msg) {
        if (verbose) {
            err.format(FMT_SKIP, msg);
        }
        return false;
    }
}
TOP

Related Classes of org.jnode.command.file.CpCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.