Package org.jnode.command.archive

Source Code of org.jnode.command.archive.Zip$Walker

/*
* $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.archive;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.jnode.command.util.AbstractDirectoryWalker;
import org.jnode.command.util.AbstractDirectoryWalker.ModTimeFilter;
import org.jnode.command.util.AbstractDirectoryWalker.PathnamePatternFilter;
import org.jnode.shell.PathnamePattern;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.StringArgument;

/**
* TODO test return codes
* TODO make sure no archive is created if the creation fails
* TODO implement delete
* TODO implement update
* TODO implement freshen
* @author chris boeriten
*/
public class Zip extends ArchiveCommand {

    private static final boolean DEBUG = true;
   
    private static final String help_delete = "remove the list of entries from the archive";
    private static final String help_freshen = "[zip] Replaces entries in the archive with files from the file " +
                                               "system if they exist and are newer than the entry.\n[unzip] Replaces " +
                                               "files on the file system with entries from the archive if they exist " +
                                               "and are newer than the file.";
    private static final String help_update = "Like freshen, except it will also add files if they do not exist";
    private static final String help_test = "[zip] Tests the archive before finishing. If the archive is corrupt " +
                                            "then the original archive is restored, if any. This will also skip " +
                                            "deleting files in a move operation.\n[unzip] Tests the archive, " +
                                            "reporting wether the archive is corrupt or not.";
    private static final String help_move = "add the list of files to the archive, removing them from the file system";
    private static final String help_list = "list the contents of the archive";
    private static final String help_no_path = "store/extract the file with only its file name and no path prefix";
    private static final String help_archive = "the zip archive to use";
    private static final String help_patterns = "file matching patterns(wildcards)";
   
    private static final String fmt_extract  = "%11s: %s";
    private static final String fmt_footer   = " %8d %8d                   %d files";
    private static final String fmt_entry    = " %8d %8d                 %d %s";
    private static final String fmt_warn_dup = "%s: %s: %s";
    private static final String fatal_create_arch  = "Could not create archive: ";
    private static final String fatal_req_arch     = "Archive required but not found: ";
    private static final String fatal_create_zfile = "Unable to open archive as ZipFile: ";
    private static final String fatal_inv_args     = "zip error: Invalid arguments (cannot repeat names in zip file)";
    private static final String fatal_walking      = "Exception while walking.";
    private static final String fatal_read_stdin   = "Exception while reading stdin.";
    private static final String str_header_1    = "   Size    CSize     Date   Time   M Name";
    private static final String str_header_2    = " -------- -------- -------- -----  - ----";
    private static final String str_footer      = " -------- --------                   -------";
    private static final String str_archive     = "Archive: ";
    private static final String str_creating    = "creating";
    private static final String str_inflating   = "inflating";
    private static final String str_adding      = "adding";
    private static final String str_zip_warn    = "zip warning";
    private static final String str_fullname_1  = " first full name";
    private static final String str_fullname_2  = "second full name";
    private static final String str_name_repeat = "name in zip file repeated";
   
    protected final StringArgument Patterns;
    protected final FileArgument Archive;
    protected final FlagArgument Delete;
    protected final FlagArgument Freshen;
    protected final FlagArgument Update;
    protected final FlagArgument Test;
    protected final FlagArgument Move;
    protected final FlagArgument List;
    protected final FlagArgument NoPath;
   
    private static final int ZIP_ADD      = 0x01;
    private static final int ZIP_MOVE     = 0x02;
    private static final int ZIP_EXTRACT  = 0x04;
    private static final int ZIP_DELETE   = 0x08;
    private static final int ZIP_LIST     = 0x10;
    private static final int ZIP_TEST     = 0x20;
    private static final int ZIP_FRESHEN  = 0x40;
    private static final int ZIP_UPDATE   = 0x80;
    private static final int ZIP_ALL      = 0x3F;
    private static final int ZIP_INSERT   = ZIP_ADD | ZIP_MOVE;
    @SuppressWarnings("unused")
    private static final int ZIP_REQ_ARCH = ZIP_ALL & ~ZIP_INSERT;
   
    /* Populated in ZipCommand and UnzipCommand */
    protected List<String> includes;
    protected List<String> excludes;
    protected List<String> excludeDirs;
   
    private List<File> files;
    private List<ZipEntry> fileEntries;
    private List<ZipEntry> dirEntries;
   
    protected long newer;
    protected long older;
    private File archive;
    private ZipFile zarchive;
    protected File tmpDir;
    protected String[] noCompress;
    protected int mode;
    protected boolean ignore_case;
    protected boolean keep;
    protected boolean overwrite;
    protected boolean backup;
    protected boolean noDirEntry;
    protected boolean noPath;
    protected boolean recurse;
    protected boolean filesStdin;
    protected boolean useStdout;
   
    public Zip(String s) {
        super(s);
        Delete  = new FlagArgument("delete", Argument.OPTIONAL, help_delete);
        Freshen = new FlagArgument("freshen", Argument.OPTIONAL, help_freshen);
        Update  = new FlagArgument("update", Argument.OPTIONAL, help_update);
        Test    = new FlagArgument("test", Argument.OPTIONAL, help_test);
        Move    = new FlagArgument("move", Argument.OPTIONAL, help_move);
        List    = new FlagArgument("list", Argument.OPTIONAL, help_list);
        NoPath  = new FlagArgument("no-path", Argument.OPTIONAL, help_no_path);
        Patterns = new StringArgument("patterns", Argument.OPTIONAL | Argument.MULTIPLE, help_patterns);
        Archive = new FileArgument("archive", Argument.MANDATORY, help_archive);
    }
   
    public void execute(String command) {
        super.execute("zcat");
        parseOptions(command);
       
        try {
            if ((mode & ZIP_ADD) != 0) {
                insert();
                if ((mode & ZIP_MOVE) != 0) {
                    for (File file : files) {
                        file.delete();
                    }
                }
                return;
            }
           
            if (mode == ZIP_EXTRACT) {
                extract();
                return;
            }
           
            if (mode == ZIP_LIST) {
                list();
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(zarchive);
            exit(0);
        }
    }
   
    private void insert() throws IOException {
        ZipOutputStream zout = null;
        ZipEntry entry;
        InputStream in;
       
        try {
            zout = new ZipOutputStream(archive);
            for (File file : files) {
                in = null;
                entry = createEntry(file);
                out(String.format(fmt_extract, str_adding, entry.getName()));
                try {
                    if (!file.isDirectory()) {
                        if ((in = openFileRead(file)) == null) {
                            continue;
                        }
                        zout.putNextEntry(entry);
                        processStream(in, zout);
                    } else {
                        zout.putNextEntry(entry);
                    }
                    zout.closeEntry();
                } catch (IOException e) {
                    debug(e.getMessage());
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            // ignore
                        }
                    }
                }
            }
        } finally {
            if (zout != null) {
                try {
                    zout.finish();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }
   
    private void list() throws IOException {
        int size = 0;
        int csize = 0;
        int count = 0;
       
        printListHeader();
        for (ZipEntry entry : dirEntries) {
            printListEntry(entry);
            count++;
        }
       
        for (ZipEntry entry : fileEntries) {
            printListEntry(entry);
            count++;
            size  += entry.getSize();
            csize += entry.getCompressedSize();
        }
        printListFooter(size, csize, count);
    }
   
    private void extract() throws IOException {
        InputStream in = null;
        OutputStream out = null;
        File file;
       
        out(str_archive + archive.getName());
       
        for (ZipEntry entry : dirEntries) {
            out(String.format(fmt_extract, str_creating, entry.getName()));
            file = new File(entry.getName());
            file.mkdirs();
        }
       
        for (ZipEntry entry : fileEntries) {
            out(String.format(fmt_extract, str_inflating, entry.getName()));
            file = new File(entry.getName());
            try {
                File parent = file.getParentFile();
                if (parent != null && !parent.exists()) {
                    parent.mkdirs();
                }
                file.createNewFile();
                in = zarchive.getInputStream(entry);
                if ((out = openFileWrite(file, false, false)) == null) {
                    continue;
                }
                processStream(in, out);
            } catch (IOException e) {
                debug(e.getMessage());
            } finally {
                close(in);
                close(out);
            }
        }
    }
   
    /**
     * Creates a ZipEntry for the specified file.
     *
     * If the file is a directory it will have a trailing slash
     * appended to its name. This is used to distinguish files
     * from directories in the archive.
     *
     * If the -j option is given, then only the file name is stored,
     * not its full pathname. Conflicts are dealt with in parseFiles()
     */
    private ZipEntry createEntry(File file) {
        String name = file.getPath();
        ZipEntry entry;
        if (file.isDirectory()) {
            if (!name.endsWith(File.separator)) {
                name = name + File.separator;
            }
            entry = new ZipEntry(name);
            entry.setMethod(ZipEntry.STORED);
        } else {
            if (noPath) {
                name = file.getName();
            }
            entry = new ZipEntry(name);
            entry.setMethod(ZipEntry.DEFLATED);
            if (noCompress != null && noCompress.length > 0) {
                for (String suf : noCompress) {
                    if (name.endsWith(suf)) {
                        entry.setMethod(ZipEntry.STORED);
                        break;
                    }
                }
            }
        }
        return entry;
    }
   
    private void parseOptions(String command) {
        if (DEBUG || Debug.isSet()) {
            outMode |= OUT_DEBUG;
        }
        if (Verbose.isSet()) {
            outMode |= OUT_NOTICE;
        }
        if (Quiet.isSet()) {
            outMode = 0;
        }
        if (command.equals("zip")) {
            if (Delete.isSet()) {
                mode = ZIP_DELETE;
            } else if (Freshen.isSet()) {
                mode = ZIP_FRESHEN | ZIP_ADD;
            } else if (Update.isSet()) {
                mode = ZIP_UPDATE | ZIP_ADD;
            } else if (Move.isSet()) {
                mode = ZIP_MOVE | ZIP_ADD;
            } else {
                mode = ZIP_ADD;
            }
        } else if (command.equals("unzip")) {
            if (Freshen.isSet()) {
                mode = ZIP_FRESHEN | ZIP_EXTRACT;
            } else if (Update.isSet()) {
                mode = ZIP_UPDATE | ZIP_EXTRACT;
            } else if (List.isSet()) {
                mode = ZIP_LIST;
            } else if (Test.isSet()) {
                mode = ZIP_TEST;
            } else {
                mode = ZIP_EXTRACT;
            }
        }
       
        noPath = NoPath.isSet();
       
        switch (mode & (ZIP_ADD | ZIP_EXTRACT | ZIP_LIST)) {
            case ZIP_ADD :
                parseFiles();
                getArchive(true, false);
                break;
            case ZIP_EXTRACT :
                getArchive(false, true);
                parseEntries();
                break;
            case ZIP_LIST :
                getArchive(false, true);
                parseEntries();
                break;
            default :
                throw new UnsupportedOperationException("This mode is not implemented.");
        }
    }
   
    /**
     * Instantiates the archive.
     *
     * If zipfile is true, than a ZipFile object for the archive is also created.
     *
     * This will exit with an error if:
     * - The archive does not exist, create is true, but an exception was thrown on creation.
     * - The archive does not exist, and create is false.
     * - The archive exists and create is true. (FIXME)
     * - A ZipFile was requested and there was an exception during instantiation.
     */
    private void getArchive(boolean create, boolean zipfile) {
        archive = Archive.getValue();
       
        if (archive.getName().equals("-")) {
            // pipe to stdout
        }
       
        if (!archive.exists()) {
            if (create) {
                try {
                    archive.createNewFile();
                } catch (IOException e) {
                    fatal(fatal_create_arch + archive, 1);
                }
            } else {
                fatal(fatal_req_arch + archive, 1);
            }
        } else {
            if (create) {
                fatal("Archive exists, refused to overwrite: " + archive, 1);
            }
        }
       
        if (zipfile) {
            try {
                zarchive = new ZipFile(archive);
            } catch (IOException e) {
                debug(e.getMessage());
                fatal(fatal_create_zfile + archive, 1);
            }
        }
    }
   
    private class Walker extends AbstractDirectoryWalker {
        @Override
        public void handleFile(final File file) throws IOException {
            addFile(file);
        }
        @Override
        public void handleDir(final File file) throws IOException {
            assert !(noDirEntry || noPath) : "handleDir called when noDirEntry || noPath";
            addFile(file);
        }
    }
   
    /**
     * Creates a list of files based on the files and filename patterns given
     * on the command line. If we're using recursion, then a directory walker
     * is used with the given include/exclude filters if any are in use.
     *
     * The -D/-j options prevent directories from being listed.
     * The -x/-i options are used to add exclude/include patterns.
     * The -r option turns on recursion
     *
     * This will exit with an error if there was an exception while walking.
     */
    private void parseFiles() {
        files = new ArrayList<File>();
        List<File> dirs = new ArrayList<File>();
       
        if (filesStdin) {
            parseFilesStdin();
        }
        if (Patterns.isSet()) {
            for (String pattern : Patterns.getValues()) {
                if (!PathnamePattern.isPattern(pattern)) {
                    File file = new File(pattern);
                    if (!file.exists()) {
                        debug("File or Directory does not exist: " + file);
                        continue;
                    }
                    if (file.isDirectory()) {
                        dirs.add(file);
                    } else {
                        addFile(file);
                    }
                } else {
                    PathnamePattern pat = PathnamePattern.compilePathPattern(pattern);
                    List<String> list = pat.expand(new File("."));
                    for (String name : list) {
                        File file = new File(name);
                        if (file.isDirectory()) {
                            dirs.add(file);
                        } else {
                            addFile(file);
                        }
                    }
                }
            }
        }
       
        if (recurse && dirs.size() > 0) {
            Walker walker = new Walker();
            if (noDirEntry || noPath) {
                walker.addFilter(new FileFilter() {
                    @Override
                    public boolean accept(File file) {
                        return !file.isDirectory();
                    }
                });
            }
            if (excludes != null && excludes.size() > 0) {
                for (String pattern : excludes) {
                    walker.addFilter(new PathnamePatternFilter(pattern, true));
                }
            }
            if (includes != null && includes.size() > 0) {
                for (String pattern : includes) {
                    walker.addFilter(new PathnamePatternFilter(pattern, false));
                }
            }
            if (newer > 0) {
                walker.addFilter(new ModTimeFilter(newer, true));
            }
            if (older > 0) {
                walker.addFilter(new ModTimeFilter(older, false));
            }
            try {
                walker.walk(dirs);
            } catch (IOException e) {
                debug(e.getMessage());
                fatal(fatal_walking, 1);
            }
        }
    }
   
    /**
     * Parses files from stdin, one file per line.
     *
     * If the file does not exist, it is ignored and omitted.
     *
     * This will exit with an error if there is an exception while reading stdin.
     */
    private void parseFilesStdin() {
        LineNumberReader reader = new LineNumberReader(stdinReader);
        String line;
        File file;
       
        try {
            while ((line = reader.readLine()) != null) {
                file = new File(line);
                if (file.exists()) {
                    addFile(file);
                }
            }
        } catch (IOException e) {
            fatal(fatal_read_stdin, 1);
        }
    }
   
    /**
     * Adds a file to the list of files.
     *
     * If the -j option is used, then the list is scanned for the file name.
     * This is done because -j strips the path from the pathname, which can cause
     * collisions if two files from separate directories with the same name are added.
     */
    private void addFile(File file) {
        if (noPath) {
            // this isn't effecient by any means, but this is not an often-used
            // case, and when it is, its not likely that files.size() is going
            // to grow very large.
            for (File f : files) {
                if (f.getName().equals(file.getName())) {
                    printDuplicateError(file, f);
                    fatal(fatal_inv_args, 1);
                }
            }
        }
        files.add(file);
    }
   
    @SuppressWarnings("unchecked")
    private void parseEntries() {
        int count = 0;
       
        ZipEntry entry;
        Enumeration<ZipEntry> entries = zarchive.getEntries();
        fileEntries = new ArrayList<ZipEntry>();
        dirEntries = new ArrayList<ZipEntry>();
       
        while (entries.hasMoreElements()) {
            count++;
            entry = entries.nextElement();
            if (entry.isDirectory()) {
                dirEntries.add(entry);
            } else {
                fileEntries.add(entry);
            }
        }
    }
   
    private void printListHeader() {
        out(str_header_1);
        out(str_header_2);
    }
   
    private void printListEntry(ZipEntry entry) {
        out(String.format(fmt_entry, entry.getSize(), entry.getCompressedSize(), entry.getMethod(), entry.getName()));
    }
   
    private void printListFooter(int size, int csize, int numFiles) {
        out(str_footer);
        out(String.format(fmt_footer, size, csize, numFiles));
    }
   
    private void printDuplicateError(File A, File B) {
        error(String.format(fmt_warn_dup, str_zip_warn, str_fullname_1, A.getPath()));
        error(String.format(fmt_warn_dup, str_zip_warn, str_fullname_2, B.getPath()));
        error(String.format(fmt_warn_dup, str_zip_warn, str_name_repeat, A.getName()));
    }
   
    @SuppressWarnings("unused")
    private void printName(String s) {
        if (outMode != 0) {
            out(s);
        }
    }
   
    @SuppressWarnings("unused")
    private void debug(ZipEntry entry) {
        debug("Name: " + entry.getName());
        debug("Directory: " + entry.isDirectory());
        debug("Platform: " + entry.getPlatform());
        debug("Mode: " + entry.getUnixMode());
        debug("IAttr: " + entry.getInternalAttributes());
        debug("EAttr: " + entry.getExternalAttributes());
        debug("CSize: " + entry.getCompressedSize());
        debug("Size: " + entry.getSize());
        debug("MTime: " + entry.getTime());
        debug("Method: " + entry.getMethod());
        debug("CRC: " + entry.getCrc());
        debug("Comment: " + entry.getComment());
        ZipExtraField[] extra = entry.getExtraFields();
        if (extra != null && extra.length > 0) {
            debug("--Extra--");
            for (ZipExtraField field : extra) {
                debug("CDL: " + field.getCentralDirectoryLength().getValue() + " FDL: " +
                                field.getLocalFileDataLength().getValue());
            }
        }
    }
}
TOP

Related Classes of org.jnode.command.archive.Zip$Walker

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.