All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.truevfs.samples.access.TrueVFS Maven / Gradle / Ivy

/*
 * Copyright © 2005 - 2021 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.truevfs.samples.access;

import net.java.truecommons.shed.BitField;
import net.java.truevfs.access.*;
import net.java.truevfs.comp.tardriver.TarDriver;
import net.java.truevfs.comp.zipdriver.JarDriver;
import net.java.truevfs.comp.zipdriver.ZipDriver;
import net.java.truevfs.driver.sfx.ReadOnlySfxDriver;
import net.java.truevfs.driver.tar.bzip2.TarBZip2Driver;
import net.java.truevfs.driver.tar.gzip.TarGZipDriver;
import net.java.truevfs.driver.tar.xz.TarXZDriver;
import net.java.truevfs.kernel.spec.FsAccessOption;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.*;

import static java.lang.System.err;
import static java.lang.System.out;
import static java.nio.file.Files.*;

/**
 * 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 this utility class features some optional archive drivers * which provide additional safety or otherwise unavailable features. * Some of these features are not normally used because of their negative * performance impact and hence this utility class should not be used for a * performance benchmark. *

* 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, some considerable amount of time is spent searching * for the Central Directory required to be present in ZIP (and hence SFX) * files. * * @author Christian Schlichtherle */ public enum TrueVFS { // This enum needs to be first, all remaining enums should be sorted alphabetically. USAGE { @Override void run(Deque args) { throw new IllegalArgumentException(); } }, CAT { @Override void run(final Deque args) throws IOException { if (1 > args.size()) { throw new NoSuchElementException(); } for (final String arg : args) { final TPath path = new TPath(arg); try (InputStream in = newInputStream(path)) { TFile.cat(in, out); } } } }, COMPACT { @Override void run(final Deque args) throws IOException { if (1 > args.size()) { throw new NoSuchElementException(); } for (final String arg : args) { final TFile file = new TFile(arg); if (file.isTopLevelArchive()) file.compact(); else err.println(message("ntlaf", file)); } } }, CP { @Override void run(Deque args) throws IOException { cpOrMv(options(args, CpOption.class), args); } }, EXISTS { @Override void run(Deque args) { out.println(exists(new TPath(args.pop()))); } }, HELP { @Override void run(Deque args) { out.println(TrueVFS.valueOf(args.pop().toUpperCase(Locale.ENGLISH)).getUsage()); } }, ISARCHIVE { @Override void run(Deque args) { out.println(new TPath(args.pop()).isArchive()); } }, ISDIRECTORY { @Override void run(Deque args) { out.println(isDirectory(new TPath(args.pop()))); } }, ISFILE { @Override void run(Deque args) { out.println(isRegularFile(new TPath(args.pop()))); } }, LS { @Override void run(Deque args) throws IOException { ls(options(args, LsOption.class), args); } }, LL { @Override void run(final Deque args) throws IOException { args.push("-l"); LS.run(args); } @Override String getUsage() { return LS.getUsage(); } }, LLR { @Override void run(final Deque args) throws IOException { args.push("-r"); args.push("-l"); LS.run(args); } @Override String getUsage() { return LS.getUsage(); } }, MKDIR { @Override void run(final Deque args) throws IOException { final boolean recursive = options(args, MkdirOption.class).get(MkdirOption.P); if (args.size() < 1) { throw new IllegalArgumentException(); } for (final String arg : args) { final TPath path = new TPath(arg); if (recursive) { createDirectories(path); } else { createDirectory(path); } } } }, MKDIRS { @Override void run(final Deque args) throws IOException { args.push("-p"); MKDIR.run(args); } @Override String getUsage() { return MKDIR.getUsage(); } }, MV { @Override void run(Deque args) throws IOException { cpOrMv(BitField.noneOf(CpOption.class), args); } }, RM { @Override void run(final Deque args) throws IOException { final boolean recursive = options(args, RmOption.class) .get(RmOption.R); if (1 > args.size()) { throw new NoSuchElementException(); } for (final String arg : args) { final TPath path = new TPath(arg); if (recursive) { walkFileTree(path, new RmVisitor()); } else { delete(path); } } } }, RMR { @Override void run(final Deque args) throws IOException { args.push("-r"); RM.run(args); } @Override String getUsage() { return RM.getUsage(); } }, SIZE { @Override void run(Deque args) throws IOException { out.println(size(new TPath(args.pop()))); } }, TOUCH { @Override void run(final Deque args) throws IOException { if (1 > args.size()) throw new NoSuchElementException(); for (final String arg : args) { final TPath path = new TPath(arg); try { createFile(path); } catch (FileAlreadyExistsException ex) { setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis())); } } } }, VERSION { @Override void run(Deque args) { out.println(message("version", TrueVFS.class.getSimpleName())); } }; /** * Scans the given command parameters for options of the given class. * As a side effect, any found options are popped off the parameter stack. * * @param the type of the enum class for the options. * @param args the command arguments. * @param option the enum class for the options. * @return a bit field of the options found. */ private static > BitField options(final Deque args, final Class option) { BitField options = BitField.noneOf(option); for (String arg; null != (arg = args.peek()) && '-' == arg.charAt(0); args.pop()) { arg = arg.substring(1).toUpperCase(Locale.ENGLISH); options = options.set(valueOf(option, arg)); } return options; } void cpOrMv( final BitField options, final Deque args) throws IOException { try (final TConfig config = TConfig.open()) { TArchiveDetector srcDetector; if (options.get(CpOption.CP437IN)) { srcDetector = newArchiveDetector(Charset.forName("IBM437")); } else if (options.get(CpOption.UTF8IN)) { srcDetector = newArchiveDetector(StandardCharsets.UTF_8); } else { srcDetector = config.getArchiveDetector(); } TArchiveDetector dstDetector; if (options.get(CpOption.UNZIP)) { dstDetector = TArchiveDetector.NULL; } else if (options.get(CpOption.CP437OUT)) { dstDetector = newArchiveDetector(Charset.forName("IBM437")); } else if (options.get(CpOption.UTF8OUT)) { dstDetector = newArchiveDetector(StandardCharsets.UTF_8); } else { dstDetector = config.getArchiveDetector(); } config.setAccessPreferences(config.getAccessPreferences() .set(FsAccessOption.STORE, options.get(CpOption.STORE)) .set(FsAccessOption.COMPRESS, options.get(CpOption.COMPRESS)) .set(FsAccessOption.GROW, options.get(CpOption.GROW)) .set(FsAccessOption.ENCRYPT, options.get(CpOption.ENCRYPT))); final TFile last = new TFile(args.removeLast(), dstDetector); final boolean expandPath = last.isDirectory(); if (args.isEmpty() || 1 < args.size() && !expandPath) { throw new IllegalArgumentException(); } for (final String arg : args) { final TFile src = new TFile(arg, srcDetector); final TFile dst = expandPath ? new TFile(last, src.getName(), dstDetector) : last; if (equals(MV)) { try { if (dst.isFile()) { dst.rm(); } src.mv(dst); } catch (final IOException ex) { throw new IOException(message("cmt", src, dst), ex); } } else { // cp if (options.get(CpOption.R)) { if (options.get(CpOption.P)) { TFile.cp_rp(src, dst, srcDetector, dstDetector); } else { TFile.cp_r(src, dst, srcDetector, dstDetector); } } else if (options.get(CpOption.P)) { TFile.cp_p(src, dst); } else { TFile.cp(src, dst); } } } } } private static TArchiveDetector newArchiveDetector(final Charset charset) { return new TArchiveDetector( TArchiveDetector.ALL, new Object[][]{ { "ear|jar|war", new JarDriver() }, { "exe", new ReadOnlySfxDriver() { @Override public Charset getCharset() { return charset; } } }, { "tar", new TarDriver() { @Override public Charset getCharset() { return charset; } } }, { "tar.bz2|tar.bzip2|tb2|tbz|tbz2", new TarBZip2Driver() { @Override public Charset getCharset() { return charset; } } }, { "tar.gz|tar.gzip|tgz", new TarGZipDriver() { @Override public Charset getCharset() { return charset; } } }, { "tar.xz|txz", new TarXZDriver() { @Override public Charset getCharset() { return charset; } } }, { "zip", new ZipDriver() { @Override public Charset getCharset() { return charset; } } }, } ); } void ls( final BitField options, final Deque args) throws IOException { if (0 >= args.size()) { args.push("."); } final boolean multi = 1 < args.size(); for (final String arg : args) { final TFile file = new TFile(arg); if (multi) { out.println(arg + ":"); } if (file.isDirectory()) { ls(options, file, ""); } else { ls(options, file, file.getPath()); } } } private void ls( final BitField options, final TFile file, final String path) throws IOException { if (file.isDirectory()) { final TFile[] members = file.listFiles(); if (null == members) { throw new IOException(message("dina", path)); } // Sort directories to the start. Arrays.sort(members, new TFileComparator()); for (final TFile member : members) { String memberPath = member.getName(); if (!path.isEmpty()) { memberPath = path + TFile.separator + memberPath; } ls(options, member.toPath(), memberPath); if (options.get(LsOption.R) && member.isDirectory()) { ls(options, member, memberPath); } } } else if (file.exists()) { ls(options, file.toPath(), path); } else { throw new IOException(message("nsfod", path)); } } private static void ls( final BitField options, final TPath file, final String path) throws IOException { final BasicFileAttributes attr = readAttributes(file, BasicFileAttributes.class); final boolean detailed = options.get(LsOption.L); if (detailed) { out.printf("%,11d %tF %'); } if (attr.isOther()) { out.append('?'); } } out.println(); } public static void main(String[] args) { System.exit(main(new LinkedList<>(Arrays.asList(args)))); } @SuppressWarnings("CallToThreadDumpStack") private static int main(final Deque args) { final TrueVFS command; try { command = valueOf(args.pop().toUpperCase(Locale.ENGLISH)); } catch (final IllegalArgumentException | NoSuchElementException ex) { final StringBuilder builder = new StringBuilder(25 * 80); for (final TrueVFS truevfs : values()) { if (0 != builder.length()) { builder.append('\n'); } builder.append(truevfs.getSynopsis()); } err.println(builder.toString()); return 1; } try { command.run(args); } catch (final IllegalArgumentException | NoSuchElementException ex) { err.println(command.getUsage()); return 2; } catch (IOException ex) { ex.printStackTrace(); return 3; } return 0; } String getSynopsis() { return message("synopsis", TrueVFS.class.getSimpleName()); } String getUsage() { return message("usage"); } String message(String key, Object... args) { return String.format( ResourceBundle .getBundle(TrueVFS.class.getName() + "." + name()) .getString(key), args); } /** * Runs this command. * Implementations are free to modify the given deque. * * @param args the command arguments. * @throws IOException on any I/O error. */ abstract void run(Deque args) throws IOException; private enum LsOption {L, R} private enum CpOption {P, R, UNZIP, CP437IN, CP437OUT, UTF8IN, UTF8OUT, STORE, COMPRESS, GROW, ENCRYPT} private enum MkdirOption {P} private enum RmOption {R} private static class RmVisitor extends SimpleFileVisitor { @Override public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs) throws IOException { delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory( final Path dir, final @Nullable IOException exc) throws IOException { if (null != exc) { throw exc; } delete(dir); return FileVisitResult.CONTINUE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy