/*
 ** Authored by Timothy Gerard Endres
 ** <mailto:time@gjt.org>  <http://www.trustice.com>
 ** 
 ** This work has been placed into the public domain.
 ** You may use this work in any way and for any purpose you wish.
 **
 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
 ** REDISTRIBUTION OF THIS SOFTWARE. 
 ** 
 */

package edu.uoregon.tau.common.tar;

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.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * The tar class implements a weak reproduction of the
 * traditional UNIX tar command. It currently supports
 * creating, listing, and extracting from archives. It
 * also supports GZIP-ed archives with the '-z' flag.
 * See the usage (-? or --usage) for option details.
 *
 * <pre>
 * usage: com.ice.tar.tar has three basic modes:
 * 
 *   com.ice.tar -c [options] archive files...
 *      Create new archive containing files.
 * 
 *   com.ice.tar -t [options] archive
 *      List contents of tar archive
 * 
 *   com.ice.tar -x [options] archive
 *      Extract contents of tar archive.
 * 
 * options:
 *    -f file, use 'file' as the tar archive
 *    -v, verbose mode
 *    -z, use GZIP compression
 *    -D, debug archive and buffer operation
 *    -b blks, set blocking size to (blks * 512) bytes
 *    -o, write a V7 format archive rather than ANSI
 *    -u name, set user name to 'name'
 *    -U id, set user id to 'id'
 *    -g name, set group name to 'name'
 *    -G id, set group id to 'id'
 *    -?, print usage information
 *    --trans, translate 'text/*' files
 *    --mime file, use this mime types file and translate
 *    --usage, print usage information
 *    --version, print version information
 * 
 * The translation options will translate from local line
 * endings to UNIX line endings of '\\n' when writing tar
 * archives, and from UNIX line endings into local line endings
 * when extracting archives.
 * 
 * Written by Tim Endres
 * This software has been placed into the public domain.
 * </pre>
 *
 * @version $Revision: 1.2 $
 * @author Timothy Gerard Endres, <time@gjt.org>
 * @see TarArchive
 */

public class Tar extends Object implements TarProgressDisplay {
    /**
     * Flag that determines if debugging information is displayed.
     */
    private boolean debug;
    /**
     * Flag that determines if verbose feedback is provided.
     */
    private boolean verbose;
    /**
     * Flag that determines if IO is GZIP-ed ('-z' option).
     */
    private boolean compressed;
    /**
     * True if we are listing the archive. False if writing or extracting.
     */
    private boolean listingArchive;
    /**
     * True if we are writing the archive. False if we are extracting it.
     */
    private boolean writingArchive;
    /**
     * True if we are writing an old UNIX archive format (sets entry format).
     */
    private boolean unixArchiveFormat;
    /**
     * True if we are not to overwrite existing files.
     */
    private boolean keepOldFiles;
    /**
     * True if we are to convert ASCII text files from local line endings
     * to the UNIX standard '\n'.
     */
    private boolean asciiTranslate;
    /**
     * True if a MIME file has been loaded with the '--mime' option.
     */
//    private boolean mimeFileLoaded;

    /**
     * The archive name provided on the command line, null if stdio.
     */
    private String archiveName;

    /**
     * The blocksize to use for the tar archive IO. Set by the '-b' option.
     */
    private int blockSize;

    /**
     * The userId to use for files written to archives. Set by '-U' option.
     */
    private int userId;
    /**
     * The userName to use for files written to archives. Set by '-u' option.
     */
    private String userName;
    /**
     * The groupId to use for files written to archives. Set by '-G' option.
     */
    private int groupId;
    /**
     * The groupName to use for files written to archives. Set by '-g' option.
     */
    private String groupName;

    /**
     * The main entry point of the tar class.
     */
    public static void main(String argv[]) {
        Tar app = new Tar();

        app.instanceMain(argv);
    }

    public static void untar(String filename) {
        String[] args = new String[2];
        args[0] = "-xf";
        args[1] = filename;
        main(args);
    }

    public static void guntar(String filename) {
        String[] args = new String[2];
        args[0] = "-xzf";
        args[1] = filename;
        main(args);
    }

    /**
     * Establishes the default userName with the 'user.name' property.
     */
    public Tar() {
        this.debug = false;
        this.verbose = false;
        this.compressed = false;
        this.archiveName = null;
        this.listingArchive = false;
        this.writingArchive = true;
        this.unixArchiveFormat = false;
        this.keepOldFiles = false;
        this.asciiTranslate = false;

        this.blockSize = TarBuffer.DEFAULT_BLKSIZE;

        String sysUserName = System.getProperty("user.name");

        this.userId = 0;
        this.userName = ((sysUserName == null) ? "" : sysUserName);

        this.groupId = 0;
        this.groupName = "";
    }

    /**
     * This is the "real" main. The class main() instantiates a tar object
     * for the application and then calls this method. Process the arguments
     * and perform the requested operation.
     */
    public void instanceMain(String argv[]) {
        TarArchive archive = null;

        int argIdx = this.processArguments(argv);

        if (writingArchive) // WRITING
        {
            OutputStream outStream = System.out;

            if (this.archiveName != null && !this.archiveName.equals("-")) {
                try {
                    outStream = new FileOutputStream(this.archiveName);
                } catch (IOException ex) {
                    outStream = null;
                    ex.printStackTrace(System.err);
                }
            }

            if (outStream != null) {
                if (this.compressed) {
                    try {
                        outStream = new GZIPOutputStream(outStream);
                    } catch (IOException ex) {
                        outStream = null;
                        ex.printStackTrace(System.err);
                    }
                }

                archive = new TarArchive(outStream, this.blockSize);
            }
        } else // EXTRACING OR LISTING
        {
            InputStream inStream = System.in;

            if (this.archiveName != null && !this.archiveName.equals("-")) {
                try {
                    inStream = new FileInputStream(this.archiveName);
                } catch (IOException ex) {
                    inStream = null;
                    ex.printStackTrace(System.err);
                }
            }

            if (inStream != null) {
                if (this.compressed) {
                    try {
                        inStream = new GZIPInputStream(inStream);
                    } catch (IOException ex) {
                        inStream = null;
                        ex.printStackTrace(System.err);
                    }
                }

                archive = new TarArchive(inStream, this.blockSize);
            }
        }

        if (archive != null) // SET ARCHIVE OPTIONS
        {
            archive.setDebug(this.debug);
            archive.setVerbose(this.verbose);
            archive.setTarProgressDisplay(this);
            archive.setKeepOldFiles(this.keepOldFiles);
            archive.setAsciiTranslation(this.asciiTranslate);

            archive.setUserInfo(this.userId, this.userName, this.groupId, this.groupName);
        }

        if (archive == null) {
            System.err.println("no processing due to errors");
        } else if (this.writingArchive) // WRITING
        {
            for (; argIdx < argv.length; ++argIdx) {
                try {
                    File f = new File(argv[argIdx]);

                    TarEntry entry = new TarEntry(f);

                    if (this.unixArchiveFormat)
                        entry.setUnixTarFormat();
                    else
                        entry.setUSTarFormat();

                    archive.writeEntry(entry, true);
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
            }
        } else if (this.listingArchive) // LISTING
        {
            try {
                archive.listContents();
            } catch (InvalidHeaderException ex) {
                ex.printStackTrace(System.err);
            } catch (IOException ex) {
                ex.printStackTrace(System.err);
            }
        } else // EXTRACTING
        {
            String userDir = System.getProperty("tar.location", null);
            if (userDir == null) {
                userDir = System.getProperty("user.dir", null);
            }
            
            File destDir = new File(userDir);
            if (!destDir.exists()) {
                if (!destDir.mkdirs()) {
                    Throwable ex = new Throwable("ERROR, mkdirs() on '" + destDir.getPath() + "' returned false.");
                    ex.printStackTrace(System.err);
                    destDir = null;
                }
            }

            if (destDir != null) {
                try {
                    archive.extractContents(destDir);
                } catch (InvalidHeaderException ex) {
                    ex.printStackTrace(System.err);
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
            }
        }

        if (archive != null) // CLOSE ARCHIVE
        {
            try {
                archive.closeArchive();
            } catch (IOException ex) {
                ex.printStackTrace(System.err);
            }
        }
    }

    /**
     * Process arguments, handling options, and return the index of the
     * first non-option argument.
     *
     * @return The index of the first non-option argument.
     */

    private int processArguments(String args[]) {
        int idx = 0;
        boolean gotOP = false;

        for (; idx < args.length; ++idx) {
            String arg = args[idx];

            if (!arg.startsWith("-"))
                break;

            if (arg.startsWith("--")) {
                if (arg.equals("--usage")) {
                    this.usage();
                    System.exit(1);
                } else if (arg.equals("--version")) {
                    this.version();
                    System.exit(1);
                } else if (arg.equals("--trans")) {
                    System.err.println("--trans option removed");
                } else if (arg.equals("--mime")) {
                    System.err.println("--mime");
                } else {
                    System.err.println("unknown option: " + arg);
                    this.usage();
                    System.exit(1);
                }
            } else
                for (int cIdx = 1; cIdx < arg.length(); ++cIdx) {
                    char ch = arg.charAt(cIdx);

                    if (ch == '?') {
                        this.usage();
                        System.exit(1);
                    } else if (ch == 'f') {
                        this.archiveName = args[++idx];
                    } else if (ch == 'z') {
                        this.compressed = true;
                    } else if (ch == 'c') {
                        gotOP = true;
                        this.writingArchive = true;
                        this.listingArchive = false;
                    } else if (ch == 'x') {
                        gotOP = true;
                        this.writingArchive = false;
                        this.listingArchive = false;
                    } else if (ch == 't') {
                        gotOP = true;
                        this.writingArchive = false;
                        this.listingArchive = true;
                    } else if (ch == 'k') {
                        this.keepOldFiles = true;
                    } else if (ch == 'o') {
                        this.unixArchiveFormat = true;
                    } else if (ch == 'b') {
                        try {
                            int blks = Integer.parseInt(args[++idx]);
                            this.blockSize = (blks * TarBuffer.DEFAULT_RCDSIZE);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace(System.err);
                        }
                    } else if (ch == 'u') {
                        this.userName = args[++idx];
                    } else if (ch == 'U') {
                        String idStr = args[++idx];
                        try {
                            this.userId = Integer.parseInt(idStr);
                        } catch (NumberFormatException ex) {
                            this.userId = 0;
                            ex.printStackTrace(System.err);
                        }
                    } else if (ch == 'g') {
                        this.groupName = args[++idx];
                    } else if (ch == 'G') {
                        String idStr = args[++idx];
                        try {
                            this.groupId = Integer.parseInt(idStr);
                        } catch (NumberFormatException ex) {
                            this.groupId = 0;
                            ex.printStackTrace(System.err);
                        }
                    } else if (ch == 'v') {
                        this.verbose = true;
                    } else if (ch == 'D') {
                        this.debug = true;
                    } else {
                        System.err.println("unknown option: " + ch);
                        this.usage();
                        System.exit(1);
                    }
                }
        }

        if (!gotOP) {
            System.err.println("you must specify an operation option (c, x, or t)");
            this.usage();
            System.exit(1);
        }

        return idx;
    }

    // I N T E R F A C E   TarProgressDisplay

    /**
     * Display progress information by printing it to System.out.
     */

    public void showTarProgressMessage(String msg) {
        System.out.println(msg);
    }

    /**
     * Print version information.
     */

    private void version() {
        System.err.println("Release 2.4 - $Revision: 1.2 $ $Name:  $");
    }

    /**
     * Print usage information.
     */

    private void usage() {
        System.err.println("usage: com.ice.tar.tar has three basic modes:");
        System.err.println("  com.ice.tar -c [options] archive files...");
        System.err.println("    Create new archive containing files.");
        System.err.println("  com.ice.tar -t [options] archive");
        System.err.println("    List contents of tar archive");
        System.err.println("  com.ice.tar -x [options] archive");
        System.err.println("    Extract contents of tar archive.");
        System.err.println("");
        System.err.println("options:");
        System.err.println("   -f file, use 'file' as the tar archive");
        System.err.println("   -v, verbose mode");
        System.err.println("   -z, use GZIP compression");
        System.err.println("   -D, debug archive and buffer operation");
        System.err.println("   -b blks, set blocking size to (blks * 512) bytes");
        System.err.println("   -o, write a V7 format archive rather than ANSI");
        System.err.println("   -u name, set user name to 'name'");
        System.err.println("   -U id, set user id to 'id'");
        System.err.println("   -g name, set group name to 'name'");
        System.err.println("   -G id, set group id to 'id'");
        System.err.println("   -?, print usage information");
        System.err.println("   --trans, translate 'text/*' files");
        System.err.println("   --mime file, use this mime types file and translate");
        System.err.println("   --usage, print usage information");
        System.err.println("   --version, print version information");
        System.err.println("");
        System.err.println("The translation options will translate from local line");
        System.err.println("endings to UNIX line endings of '\\n' when writing tar");
        System.err.println("archives, and from UNIX line endings into local line endings");
        System.err.println("when extracting archives.");
        System.err.println("");
        System.err.println("Written by Tim Endres");
        System.err.println("");
        System.err.println("This software has been placed into the public domain.");
        System.err.println("");

        this.version();

        System.exit(1);
    }

}
