de.schlichtherle.truezip.sample.file.app.Nzip Maven / Gradle / Ivy
/*
* Copyright (C) 2005-2012 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package de.schlichtherle.truezip.sample.file.app;
import de.schlichtherle.truezip.file.*;
import static de.schlichtherle.truezip.fs.FsOutputOption.*;
import de.schlichtherle.truezip.fs.FsSyncException;
import de.schlichtherle.truezip.fs.archive.tar.TarBZip2Driver;
import de.schlichtherle.truezip.fs.archive.tar.TarDriver;
import de.schlichtherle.truezip.fs.archive.tar.TarGZipDriver;
import de.schlichtherle.truezip.fs.archive.zip.CheckedJarDriver;
import de.schlichtherle.truezip.fs.archive.zip.CheckedReadOnlySfxDriver;
import de.schlichtherle.truezip.fs.archive.zip.CheckedZipDriver;
import de.schlichtherle.truezip.socket.IOPoolProvider;
import de.schlichtherle.truezip.socket.sl.IOPoolLocator;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A comprehensive command line utility which allows you to work
* with entries in all supported archive files using Unix like commands
* ({@code cat}, {@code cp}, {@code rm}, {@code mkdir},
* {@code rmdir}, {@code ls} etc.).
*
* Please note that TrueZIP is designed for optimum performance.
* However, this utility features some optional archive drivers which
* provide additional safety or otherwise unavailable features.
* Some of these drivers are not used in their default configuration -
* see {@link de.schlichtherle.truezip.file.TArchiveDetector} for more
* information.
* For example, the ZIP drivers used in this utility always check
* the CRC-32 values provided in the ZIP file.
* In addition, the SFX driver is used which allows you to browse
* {@code .exe} files if they happen to be SelF eXtracting archives (SFX).
* If they are not however, TrueZIP may spend some considerable amount of
* time searching for the Central Directory required to be present in ZIP
* (and hence SFX) files.
* As a conclusion, this utility should not serve as a performance benchmark.
*
* @author Christian Schlichtherle
*/
@NotThreadSafe
public class Nzip extends Application {
private static final ResourceBundle resources
= ResourceBundle.getBundle(Nzip.class.getName());
private static final IOPoolProvider POOL_PROVIDER = IOPoolLocator.SINGLETON;
private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
private final DateFormat dateFormat = DateFormat.getDateTimeInstance();
private final FieldPosition fpos = new FieldPosition(NumberFormat.INTEGER_FIELD);
/**
* May be overridden by subclasses to create the
* {@link TArchiveDetector} which provides file system drivers which
* should use the specified charset if supported.
*
* Note that the archive detector which is returned by the implementation
* in this class uses some archive drivers which may be pretty slow due to
* some extra compatibility tests which they perform on every archive.
*/
protected TArchiveDetector newArchiveDetector() {
return new TArchiveDetector(TArchiveDetector.ALL,
new Object[][] {
{ "ear|jar|war", new CheckedJarDriver(POOL_PROVIDER) },// check CRC-32
{ "zip", new CheckedZipDriver(POOL_PROVIDER) }, // check CRC-32
{ "exe", new CheckedReadOnlySfxDriver(POOL_PROVIDER) }, // check CRC-32
});
}
/** @see #newArchiveDetector() */
protected TArchiveDetector newArchiveDetector(final Charset charset) {
assert null != charset;
return new TArchiveDetector(TArchiveDetector.ALL,
new Object[][] {
{ "ear|jar|war|zip", new CheckedZipDriver(POOL_PROVIDER) { // check CRC-32
@Override
public Charset getCharset() {
return charset;
}
} },
{ "exe", new CheckedReadOnlySfxDriver(POOL_PROVIDER) { // check CRC-32
@Override
public Charset getCharset() {
return charset;
}
} },
{ "tar", new TarDriver(POOL_PROVIDER) {
@Override
public Charset getCharset() {
return charset;
}
} },
{ "tgz|tar.gz", new TarGZipDriver(POOL_PROVIDER) {
@Override
public Charset getCharset() {
return charset;
}
} },
{ "tbz|tb2|tar.bz2", new TarBZip2Driver(POOL_PROVIDER) {
@Override
public Charset getCharset() {
return charset;
}
} },
});
}
/** Equivalent to {@code System.exit(new Nzip().run(args));}. */
public static void main(String[] args) throws FsSyncException {
System.exit(new Nzip().run(args));
}
/**
* Runs this command line utility.
* Throws an exception if an error occurs.
*
* @param args A non-empty array of Unix-like commands and optional
* parameters.
* @return {@code false} iff the command is a test which fails,
* {@code true} otherwise.
* @throws IllegalUsageException If {@code args} does not contain
* correct commands or parameters.
* @throws IOException On any I/O error.
*/
@Override
protected int runChecked(String[] args)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
final String cmd = args[0].toLowerCase(Locale.ROOT);
args = lshift(args);
final TArchiveDetector oldDetector = TConfig.get().getArchiveDetector();
try {
// Install custom archive detector.
TConfig.get().setArchiveDetector(newArchiveDetector());
if ("ls".equals(cmd)) {
ls(args, false, false);
} else if ("ll".equals(cmd)) {
ls(args, true, false);
} else if ("llr".equals(cmd)) {
ls(args, true, true);
} else if ("cat".equals(cmd)) {
cat(args);
} else if ("compact".equals(cmd)) {
compact(args);
} else if ("cp".equals(cmd)) {
cpOrMv(args, false);
} else if ("mv".equals(cmd)) {
cpOrMv(args, true);
} else if ("touch".equals(cmd)) {
touch(args);
} else if ("mkdir".equals(cmd)) {
mkdir(args, false);
} else if ("mkdirs".equals(cmd)) {
mkdir(args, true);
} else if ("rm".equals(cmd)) {
rm(args, false);
} else if ("rmr".equals(cmd)) {
rm(args, true);
} else if ("isarchive".equals(cmd)) {
return isArchive(args) ? 0 : 1;
} else if ("isdirectory".equals(cmd)) {
return isDirectory(args) ? 0 : 1;
} else if ("isfile".equals(cmd)) {
return isFile(args) ? 0 : 1;
} else if ("exists".equals(cmd)) {
return exists(args) ? 0 : 1;
} else if ("length".equals(cmd)) {
return length(args) ? 0 : 1;
} else {
throw new IllegalUsageException();
}
} finally {
TConfig.get().setArchiveDetector(oldDetector);
}
return 0;
}
private static String[] lshift(final String[] args) {
return lshift(args, 1);
}
private static String[] lshift(final String[] args, final int num) {
final int rem = args.length - num;
if (rem < 0)
throw new IllegalArgumentException();
final String[] ret = new String[rem];
System.arraycopy(args, num, ret, 0, rem);
return ret;
}
private void ls(
String[] args,
final boolean detailed,
final boolean recursive)
throws IOException {
if (args.length <= 0)
args = new String[] { "." };
for (int i = 0; i < args.length; i++) {
final TFile file = new TFile(args[i]);
if (args.length > 1)
out.println(args[i] + ":");
if (file.isDirectory())
ls(file, "", detailed, recursive);
else
ls(file, file.getPath(), detailed, recursive);
}
}
/**
* Lists the given file with the given display path.
*/
private void ls(
final TFile file,
final String path,
final boolean detailed,
final boolean recursive)
throws IOException {
if (file.isDirectory()) {
final TFile[] entries = file.listFiles();
if (entries == null)
throw new IOException(path + " (" + resources.getString("ls.dia") + ")");
// Sort directories to the start.
Arrays.sort(entries, new TFileComparator());
for (int i = 0; i < entries.length; i++) {
final TFile entry = entries[i];
final String entryPath = path.length() > 0
? path + TFile.separator + entry.getName()
: entry.getName();
ls(entry, entryPath, detailed);
if (recursive && entry.isDirectory())
ls(entries[i], entryPath, detailed, true);
}
} else if (file.exists()) {
ls(file, path, detailed);
} else {
throw new IOException(path + " (" + resources.getString("ls.nsfod") + ")");
}
}
private void ls(
final TFile file,
final String path,
final boolean detailed) {
final StringBuffer buf = new StringBuffer();
if (detailed) {
align(buf, file.length(), 11);
buf.append(' ');
buf.append(dateFormat.format(new Date(file.lastModified())));
buf.append(' ');
}
buf.append(path);
if (detailed)
buf.append(file.isDirectory()
? (file.isFile() ? "+" : TFile.separator)
: file.isFile()
? ""
: file.exists()
? "?"
: "\u2020"); // dagger '†'
out.println(buf.toString());
}
private void align(StringBuffer buf, long number, int spacing) {
final int length = buf.length();
numberFormat.format(number, buf, fpos);
for (int i = spacing - fpos.getEndIndex(); --i >= 0; )
buf.insert(length, ' ');
}
private void cat(final String[] args)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
for (int i = 0; i < args.length; i++) {
final InputStream in = new TFileInputStream(args[i]);
try {
TFile.cat(in, out);
} finally {
in.close();
}
}
}
private void compact(String[] args)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
for (int i = 0; i < args.length; i++) {
final TFile file = new TFile(args[i]);
if (file.isArchive()) {
file.compact();
} else {
err.println(file + " (" + resources.getString("compact.na") + ")");
}
}
}
@edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") // false positive!
private void cpOrMv(final String[] args, final boolean mv)
throws IllegalUsageException, IOException {
if (args.length < 2)
throw new IllegalUsageException();
int srcI = 0;
boolean unzip = false;
boolean cp437out = false;
boolean utf8out = false;
boolean cp437in = false;
boolean utf8in = false;
boolean store = false;
boolean compress = false;
boolean grow = false;
boolean encrypt = false;
for (; srcI < args.length && args[srcI].charAt(0) == '-'; srcI++) {
if (mv) // mv
throw new IllegalUsageException();
final String opt = args[srcI].toLowerCase(Locale.ROOT);
if ("-unzip".equals(opt)) {
unzip = true;
} else if ("-cp437out".equals(opt)) {
cp437out = true;
} else if ("-utf8out".equals(opt)) {
utf8out = true;
} else if ("-cp437in".equals(opt)) {
cp437in = true;
} else if ("-utf8in".equals(opt)) {
utf8in = true;
} else if ("-store".equals(opt)) {
store = true;
} else if ("-compress".equals(opt)) {
compress = true;
} else if ("-grow".equals(opt)) {
grow = true;
} else if ("-encrypt".equals(opt)) {
encrypt = true;
} else {
throw new IllegalUsageException();
}
}
final TArchiveDetector srcDetector;
if (cp437in)
srcDetector = newArchiveDetector(Charset.forName("IBM437"));
else if (utf8in)
srcDetector = newArchiveDetector(Charset.forName("UTF-8"));
else
srcDetector = TConfig.get().getArchiveDetector();
final TArchiveDetector dstDetector;
if (unzip)
dstDetector = TArchiveDetector.NULL;
else if (cp437out)
dstDetector = newArchiveDetector(Charset.forName("IBM437"));
else if (utf8out)
dstDetector = newArchiveDetector(Charset.forName("UTF-8"));
else
dstDetector = TConfig.get().getArchiveDetector();
final int dstI = args.length - 1;
final TFile dst = new TFile(args[dstI], dstDetector);
if (dstI - srcI < 1 || (dstI - srcI > 1
&& !dst.isArchive() && !dst.isDirectory()))
throw new IllegalUsageException();
final TConfig config = TConfig.push();
try {
config.setOutputPreferences(config.getOutputPreferences()
.set(STORE, store)
.set(COMPRESS, compress)
.set(GROW, grow)
.set(ENCRYPT, encrypt));
for (int i = srcI; i < dstI; i++) {
final TFile src = new TFile(args[i], srcDetector);
final TFile tmp = dstI - srcI > 1 || dst.isDirectory()
? new TFile(dst, src.getName(), dstDetector)
: dst;
if (mv) {
try {
if (tmp.isFile())
tmp.rm();
src.mv(tmp);
} catch (IOException ex) {
throw new IOException(src + ": " + resources.getString("cpOrMv.cmt") + ": " + tmp, ex);
}
} else { // cp
TFile.cp_rp(src, tmp, srcDetector, dstDetector);
}
}
} finally {
config.close();
}
}
private void touch(final String[] args)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
for (int i = 0; i < args.length; i++) {
final TFile file = new TFile(args[i]);
final boolean ok;
if (!file.exists())
ok = file.createNewFile();
else
ok = file.setLastModified(System.currentTimeMillis());
if (!ok) {
final String msg;
if (!file.exists())
msg = resources.getString("touch.ccf");
else if (file.isDirectory())
msg = resources.getString("touch.culmtod");
else if (file.isFile())
msg = resources.getString("touch.culmtof");
else
msg = resources.getString("touch.culmtosfod");
throw new IOException(file + " (" + msg + ")");
}
}
}
private void mkdir(final String[] args, final boolean recursive)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
for (int i = 0; i < args.length; i++) {
final TFile file = new TFile(args[i]);
final boolean ok = recursive ? file.mkdirs() : file.mkdir();
if (!ok) {
final String msg;
if (!file.exists())
msg = resources.getString("mkdir.ccd");
else if (file.isDirectory())
msg = resources.getString("mkdir.dea");
else if (file.isFile())
msg = resources.getString("mkdir.fea");
else
msg = resources.getString("mkdir.sfodea");
throw new IOException(file + " (" + msg + ")");
}
}
}
private void rm(final String[] args, final boolean recursive)
throws IllegalUsageException, IOException {
if (args.length < 1)
throw new IllegalUsageException();
for (int i = 0; i < args.length; i++) {
final TFile file = new TFile(args[i]);
try {
if (recursive)
file.rm_r();
else
file.rm();
} catch (IOException ex) {
final String msg;
if (!file.exists())
msg = resources.getString("rm.nsfod");
else if (file.isDirectory())
if (file.list().length > 0)
msg = resources.getString("rm.dne");
else
msg = resources.getString("rm.crd");
else if (file.isFile())
msg = resources.getString("rm.crf");
else
msg = resources.getString("rm.crsfod");
throw new IOException(file + " (" + msg + ")", ex);
}
}
}
private boolean isArchive(final String[] args)
throws IllegalUsageException {
if (args.length != 1)
throw new IllegalUsageException();
final boolean success = new TFile(args[0]).isArchive();
out.println(success);
return success;
}
private boolean isDirectory(final String[] args)
throws IllegalUsageException {
if (args.length != 1)
throw new IllegalUsageException();
final boolean success = new TFile(args[0]).isDirectory();
out.println(success);
return success;
}
private boolean isFile(final String[] args)
throws IllegalUsageException {
if (args.length != 1)
throw new IllegalUsageException();
final boolean success = new TFile(args[0]).isFile();
out.println(success);
return success;
}
private boolean exists(final String[] args)
throws IllegalUsageException {
if (args.length != 1)
throw new IllegalUsageException();
final boolean success = new TFile(args[0]).exists();
out.println(success);
return success;
}
private boolean length(final String[] args)
throws IllegalUsageException {
if (args.length != 1)
throw new IllegalUsageException();
final long length = new TFile(args[0]).length();
out.println(length);
return true;
}
@SuppressWarnings("ProtectedInnerClass")
protected static class IllegalUsageException
extends Application.IllegalUsageException {
private static final long serialVersionUID = 2660653252314854276L;
protected IllegalUsageException() {
super(resources.getString("usage")); // use Resource Bundle
}
}
}