/*
* $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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.tools.bzip2.CBZip2InputStream;
import org.apache.tools.bzip2.CBZip2OutputStream;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarOutputStream;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.StringArgument;
/**
*
* This version of tar has the ability to magically work with compressed archives without being
* told with the -jz flags on the command line. The -jz flags are seen as an override to force tar
* to work with the selected compression method, and fail if it can't do that.
*
* Reading :
* 1) If the -j or -z flags are set, then tar will check for the magic bytes in the header for the
* given compression method. If they are not found, tar will fail.
* 2) If the -j or -z flags are not set, then tar will check for the magic bytes in the header for
* both compression methods. If either are found, tar will wrap the FileInputStream given to TarInputStream
* with a decompression wrapper stream. If neither are found, then tar assumes the archive is an
* uncompressed archive.
* If the archive is being extracted from stdin, and the archive is compressed, then the -j or -z flags
* must be given, or tar will assume it is already uncompressed.
*
* Creating :
* 1) If the -j or -z flags are set, then tar will output a new archive compressed with the given method.
* 2) If the -j or -z flags are not set, then tar will check the file suffix of the new archive. If the suffix
* is either .tbz, .tbz2, .tar.bz or .tar.bz2, then tar will compress with bzip2. If the suffix is .tar.gz
* then tar will compress with gzip. Otherwise an uncompressed archive is created.
*
* Writing :
* 1) If the -j or -z flags are set, then tar will output an archive compressed with the given method.
* 2) If the -j or -z flags are not set, and the original archive was compressed, then the same method will
* be used to recompress the archive. Otherwise the archive will be output uncompressed.
*
* TODO Currently, in order to set the output stream to the last entry of an archive, in order to append new entries,
* we're moving the archive, creating a new archive, and copying the data via streams. A more effecient method
* would be a reverse search of the file looking for the last block to be written too. Blocks are written in
* groups of 20, so it will be relatively fast to find. Once we read the header, we'll know how big the entry is
* and we can position the OutputStream to the first available block. We can't simply append to the end of the
* file because the last block is not necessarily the end of the file.
* This might not be quite so simple though, especially when dealing with compressed archives. Which can't be
* randomly accessed.
*
* TODO Implement a way to check if a file being handled is actually a tar file. The tar input stream will gladly
* read a file that is not a tar archive. This can cause any sort of irrational behavior, especially if the
* data is binary in nature.
* TODO Implement update/delete.
* TODO Implement interactive (global)
* TODO Implement display totals. (global)
* TODO Implement verification (verify)
* FIXME BUG - if extracting an archive given on stdin, and that archive overwrites files on the filesystem
* it will cause openFileWrite() to prompt the user and ask to overwrite, but reading from stdin
* wont come from the console since stdin is attached to a pipe. In this case we will either have
* to default to overwrite or skip.
* TODO When extracting entries, tar needs to strip any leading slashes by default, turning absolute pathnames
* into relative ones. When inserting entries, leading slashes should also be stripped by default. Also
* warn about inserting entries with a '..' prefix, and refuse to extract them.
* TODO Default behavior when extracting is to overwrite.
* TODO Implement exclusion by date, --newer=<date> ignores files with a mod time older than the given date.
*
* TODO Options to implement
* -P --absolute-names [Create, Append, Update] TODO
* Overrides the default behavior of stripping leading slashes while inserting or extracting entries. Will
* also force tar to extract entries prefixed with '..'
* -N --newer [All] TODO
* Limit operating on files to only those that are newer than the given date. If the value starts with a
* / or ., the value is a file, and its mod time should be used.
* --newer-mtime [All] TODO
* Similar to --newer, except it doesn't take into account status changes made to the file, only content
* changes (might not be a difference atm in jnode, so they'll both be the same).
* --atime-preserve [Create, Append, Update, Extract]
* Preserve the access time of the file. (Not important)
* -a --auto-compress [Create]
* When creating an archive, determine the compression type from the archive suffix. We already do this by
* default if no compress flags (j/z) were given.
* --no-auto-compress [Create] TODO
* Turn off the automatic checking of archive suffix to determine compression type.
* --backup [Extract] TODO
* Backup files instead of overwriting them. We currently implement simple backups using a given or default
* suffix. There is also the option of creating numbered backups. And a hybrid option that makes numbered
* backups if they exist already, simple backups if they do not. As well this option can also apply to the
* archive itself if it is being modified. This option gets its default from the VERSION_CONTROL env variable.
* If this is not set, the default is 'existing'.
* Options are 't' | 'numbered', 'nil' | 'existing' and 'never' | 'simple'.
* --suffix [Extract/backup]
* Need to read the SIMPLE_BACKUP_SUFFIX env variable for the default, otherwise use ~
* --checkpoint n
* Checkpoints are issued every nth recorded that is written or read to/from the archive.
* --checkpoing-action <action>
* Perform one of the following actions every checkpoint
* - bell Produces an audible bell on the system speaker
* - dot prints a '.' to stdout
* - echo Display a message to stderr
* - echo=string Display string to stderr, string is subject to meta-character expansion
* - exec=command Execute the given command
* - sleep=time Sleep for <time> seconds
* - ttyout=string Output string to /dev/tty (the current console, if stderr is redirected)
* This argument may be given multiple times to perform different actions. They will be executed in the
* order found on the command line.
* -l --check-links
* --delay-directory-restore
* --no-delay-directory-restore
* Restore directory timestamps and permissions after extraction has completed.
* -h --dereference
* Store the file a link points to instead of the link itself
* --anchored
* --no-anchored
* --ignore-case
* --no-ignore-case
* --wildcards
* --no-wildcards
* --wildcards-match-slash
* --no-wildcards-match-slash
* Pattern matching modifier
* --ignore-failed-read
* Do not exit just because a read of a file failed. (We do this by default atm)
* --index-file=<file>
* Send verbose output to <file>
* --overwrite
* --overwrite-dir
* Overwrite existing files when extracting (default)
*
* --lzma
* --lzop
* -Z --compress
* Other compression methods, not supported atm.
*
* --pax-option=<list>
* --owner=<name>
* -o --no-same-owner
* --no-same-permissions
* --no-unquote
* --numberic-owner
* --occurence=<number>
* --one-file-system
* --null
* --no-null
* --no-overwrite-dir
* --no-ignore-command-error
* -M --multi-volume
* --no-check-device
* -g --listed-incremental=<file>
* -V --label=<string>
* -F --info-script=<file>
* -G --incremental
* --group
* --owner
* --mode
* --mtime
* -H --format
* --interactive
* -R --block-number
* -b --blocking-factor
* -i --ignore-zeroes
* -B --read-full-records
* --check-device
* (ignore for now)
*
* @author chris boertien
*/
public class TarCommand extends ArchiveCommand {
private static final String help_append = "append entries to an archive";
private static final String help_concat = "concatenate multiple archives";
private static final String help_create = "create a new tar archive";
private static final String help_delete = "delete entries from an archive";
private static final String help_diff = "find differences between the archive and file system";
private static final String help_extract = "extract the entries from an archive";
private static final String help_list = "list the contents of an archive";
private static final String help_update = "only append files that are newer than the copy in the archive";
private static final String help_archive = "use the given archive";
private static final String help_backup = "backup files instead of overwriting";
private static final String help_bzip = "compress the archive with bzip2";
private static final String help_dir = "change to directory";
private static final String help_exclude = "exclude files matching <pattern>";
private static final String help_file_list = "get names to extract or archive from <file>";
private static final String help_gzip = "compress the archive with gzip";
private static final String help_interact = "ask for confirmation for every action";
private static final String help_keep_old = "keep existing files; don't overwrite from archive";
private static final String help_keep_new = "keep existing files if they are newer than the archive entry";
private static final String help_norecurse = "do not recurse into directories";
private static final String help_paths = "files and directories to include in archive";
private static final String help_recurse = "recurse into directories";
private static final String help_remove = "remove files after adding them to the archive";
@SuppressWarnings("unused")
private static final String help_stdout = "extract files to stdout";
private static final String help_suffix = "append <suffix> to backup files (default ~)";
private static final String help_totals = "display total bytes written after creating the archive";
private static final String help_unlink = "when extracting, delete files if they exist. This is the default" +
"action and is used to override other options if they were set";
@SuppressWarnings("unused")
private static final String help_verbose = "list files processed";
private static final String help_verify = "verify the archive after writing it";
private static final String help_xfile = "exclude files matching patterns in <file>";
private static final String err_options = "required options -Acdtrux not found, or multiple options set";
private static final int TAR_APPEND = 0x01;
private static final int TAR_CREATE = 0x02;
private static final int TAR_CONCAT = 0x04;
private static final int TAR_DELETE = 0x08;
private static final int TAR_UPDATE = 0x10;
private static final int TAR_LIST = 0x20;
private static final int TAR_DIFF = 0x40;
private static final int TAR_EXTRACT = 0x80;
private static final int TAR_REQ_ARCH
= TAR_APPEND | TAR_CREATE | TAR_CONCAT | TAR_DELETE | TAR_UPDATE | TAR_LIST | TAR_DIFF;
private static final int TAR_INSERT = TAR_APPEND | TAR_CREATE;
private static final int TAR_VERIFY = TAR_APPEND | TAR_CONCAT | TAR_DELETE | TAR_UPDATE;
private static final int TAR_COMPRESS = TAR_APPEND | TAR_CREATE | TAR_CONCAT | TAR_DELETE | TAR_UPDATE;
private static final int TAR_DECOMPRESS =
TAR_APPEND | TAR_CONCAT | TAR_DELETE | TAR_DIFF | TAR_UPDATE | TAR_LIST | TAR_EXTRACT;
private static final int USE_BZIP = 1;
private static final int USE_GZIP = 2;
private final FlagArgument DoAppend = new FlagArgument("doAppend", Argument.OPTIONAL, help_append);
private final FlagArgument DoConcat = new FlagArgument("doConcat", Argument.OPTIONAL, help_concat);
private final FlagArgument DoCreate = new FlagArgument("doCreate", Argument.OPTIONAL, help_create);
private final FlagArgument DoDelete = new FlagArgument("doDelete", Argument.OPTIONAL, help_delete);
private final FlagArgument DoDiff = new FlagArgument("doDiff", Argument.OPTIONAL, help_diff);
private final FlagArgument DoExtract = new FlagArgument("doExtract", Argument.OPTIONAL, help_extract);
private final FlagArgument DoList = new FlagArgument("doList", Argument.OPTIONAL, help_list);
private final FlagArgument DoUpdate = new FlagArgument("doUpdate", Argument.OPTIONAL, help_update);
private final FlagArgument Backup = new FlagArgument("backup", Argument.OPTIONAL, help_backup);
private final FlagArgument UseBzip = new FlagArgument("bzip", Argument.OPTIONAL, help_bzip);
private final FileArgument ChangeDir = new FileArgument("dir", Argument.OPTIONAL, help_dir);
private final StringArgument Exclude = new StringArgument("exclude", Argument.OPTIONAL, help_exclude);
private final FileArgument Archive = new FileArgument("archive", Argument.OPTIONAL, help_archive);
private final FileArgument FileList = new FileArgument("fileList", Argument.OPTIONAL, help_file_list);
private final FlagArgument UseGzip = new FlagArgument("gzip", Argument.OPTIONAL, help_gzip);
private final FlagArgument Interact = new FlagArgument("interact", Argument.OPTIONAL, help_interact);
private final FlagArgument KeepOld = new FlagArgument("keep_old", Argument.OPTIONAL, help_keep_old);
private final FlagArgument KeepNew = new FlagArgument("keep_new", Argument.OPTIONAL, help_keep_new);
private final FlagArgument NoRecurse = new FlagArgument("noRecurse", Argument.OPTIONAL, help_norecurse);
private final FlagArgument Recurse = new FlagArgument("recurse", Argument.OPTIONAL, help_recurse);
private final FlagArgument RemoveFiles = new FlagArgument("removeFiles", Argument.OPTIONAL, help_remove);
private final FlagArgument ShowTotals = new FlagArgument("showTotals", Argument.OPTIONAL, help_totals);
private final StringArgument Suffix = new StringArgument("suffix", Argument.OPTIONAL, help_suffix);
private final FlagArgument Unlink = new FlagArgument("unlink", Argument.OPTIONAL, help_unlink);
private final FlagArgument Verify = new FlagArgument("verify", Argument.OPTIONAL, help_verify);
private final FileArgument ExcludeFile = new FileArgument("xfile", Argument.OPTIONAL, help_xfile);
private final FileArgument Paths = new FileArgument("paths", Argument.OPTIONAL | Argument.MULTIPLE, help_paths);
private File archive;
@SuppressWarnings("unused")
private File excludeFile;
@SuppressWarnings("unused")
private File fileList;
private String suffix = "~";
@SuppressWarnings("unused")
private String exclude = "";
private int mode;
private int compress;
private int decompress;
private boolean recurse;
@SuppressWarnings("unused")
private boolean pipeInOut;
private boolean backup;
private boolean bzip;
private boolean gzip;
@SuppressWarnings("unused")
private boolean interact;
private boolean verify;
@SuppressWarnings("unused")
private boolean showTotals;
private boolean keepOld;
private boolean keepNew;
@SuppressWarnings("unused")
private boolean unlink;
public TarCommand() {
super("Create/Modify/Extract tape archives");
// from ArchiveCommand
registerArguments(Verbose, Debug, Stdout);
// tar Operations
registerArguments(DoAppend, DoConcat, DoCreate, DoDelete, DoDiff, DoExtract, DoList, DoUpdate);
// tar Global Options
registerArguments(Backup, Suffix, UseBzip, UseGzip, Archive, FileList, ExcludeFile, Interact, KeepNew, KeepOld,
Unlink, RemoveFiles, ShowTotals, Verify, Paths);
// tar Parsing Options
registerArguments(ChangeDir, Exclude, NoRecurse, Recurse);
}
// TODO Allow working directory to be changed
public void execute() {
super.execute("tar");
if (!checkMode()) {
fatal(err_options, 1);
}
if (Archive.isSet()) archive = Archive.getValue();
if (Suffix.isSet()) suffix = Suffix.getValue();
if (Exclude.isSet()) exclude = Exclude.getValue();
if (ExcludeFile.isSet()) excludeFile = ExcludeFile.getValue();
if (FileList.isSet()) fileList = FileList.getValue();
backup = Backup.isSet();
bzip = UseBzip.isSet();
gzip = UseGzip.isSet();
interact = Interact.isSet();
verify = Verify.isSet();
showTotals = ShowTotals.isSet();
keepOld = KeepOld.isSet();
keepNew = KeepNew.isSet();
recurse = !NoRecurse.isSet();
unlink = Unlink.isSet();
try {
if ((mode & TAR_REQ_ARCH) != 0 && archive == null) {
fatal("Archive required for -Acdtru", 1);
}
if ((mode & TAR_DECOMPRESS) != 0 && archive != null) {
if (checkCompressed(archive) == -1) {
// happens when -j or -z were specified, but the archive is not
// in the given format.
if (bzip) {
fatal("Archive is not compressed with bzip2.", 1);
}
if (gzip) {
fatal("Archive is not compressed with gzip.", 1);
}
fatal("Internal Error: checkCompressed() returned -1", -1);
}
}
if ((mode & TAR_COMPRESS) != 0) {
if (bzip) {
compress = USE_BZIP;
} else if (gzip) {
compress = USE_GZIP;
} else {
compress = decompress;
}
}
if ((mode & TAR_VERIFY) != 0 && verify) {
// backup original archive
}
if ((mode & TAR_CREATE) != 0 && compress == 0) {
compress = checkSuffix(archive);
}
if ((mode & TAR_INSERT) != 0) {
insert(processFiles(Paths.getValues(), recurse));
}
if ((mode & TAR_UPDATE) != 0) {
update(processFiles(Paths.getValues(), recurse));
}
if ((mode & TAR_CONCAT) != 0) {
concat(processArchives(Paths.getValues()));
}
if ((mode & TAR_DELETE) != 0) {
//delete();
}
if ((mode & TAR_EXTRACT) != 0) {
if (decompress == 0 && archive == null) {
if (bzip) {
decompress = USE_BZIP;
} else if (gzip) {
decompress = USE_GZIP;
}
}
extract();
}
if ((mode & TAR_LIST) != 0) {
list();
}
if ((mode & TAR_DIFF) != 0) {
diff();
}
} catch (Exception e) {
e.printStackTrace();
fatal(err_exception_uncaught, 1);
}
}
/**
* Concatenates a list of archives with this archive.
*
* TODO If the verify option is set, the original archive is backed up before operating
* on it, and verified before exiting. If the archive is bad, the original is restored.
*/
private void concat(File[] archives) throws IOException {
InputStream in;
TarInputStream tin;
TarOutputStream tout;
// Setup archive for appending
tout = appendTarOutputStream();
// Concatenate new archives
for (File arch : archives) {
if ((in = openFileRead(arch)) == null) {
continue;
}
bzip = gzip = false;
decompress = checkCompressed(arch);
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
copy(tin, tout);
}
tout.close();
}
/**
* Insert a list of files into an archive.
*
* This is used by Create and Append to insert new entries into the archive.
*
* TODO Allow files to be delete from the filesystem after the archive has been written.
* If the verify option is set, then the archive must pass verification before the
* files are deleted.
*
* TODO If the verify option is set, the original archive is backed up before operating
* on it, and verified before exiting. If the archive is bad, the original is restored.
*/
private void insert(File[] files) throws IOException {
InputStream in;
OutputStream out;
TarOutputStream tout = null;
TarEntry entry;
if (mode == TAR_APPEND && archive.exists()) {
tout = appendTarOutputStream();
} else {
createArchive();
if ((out = openFileWrite(archive, false, false)) == null) {
fatal(" ", 1);
}
if (compress != 0) {
out = wrapOutputStream(out);
}
tout = new TarOutputStream(out);
}
// Insert new entries
for (File file : files) {
notice(file.getPath());
entry = new TarEntry(file);
tout.putNextEntry(entry);
if (!file.isDirectory()) {
if ((in = openFileRead(file)) == null) continue;
processStream(in, tout);
in.close();
}
tout.closeEntry();
}
tout.close();
}
// TODO
private void update(File[] files) throws IOException {
InputStream in;
TarInputStream tin;
TarEntry entry;
TreeMap<String, Long> entries = new TreeMap<String, Long>();
if ((in = openFileRead(archive)) == null) {
fatal(" ", 1);
}
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
while ((entry = tin.getNextEntry()) != null) {
entries.put(entry.getName(), entry.getModTime().getTime());
}
tin.close();
long etime, ftime;
ArrayList<File> list = new ArrayList<File>();
for (File file : files) {
if (entries.containsKey(file.getPath())) {
etime = entries.get(file.getPath());
ftime = file.lastModified();
if (etime >= ftime) {
continue;
}
}
list.add(file);
}
insert(list.toArray(files));
}
// TODO
@SuppressWarnings("unused")
private void delete(String[] names) throws IOException {
}
/**
* Extract entries from an archive.
*
* TODO Need to parse Path for choosing specific files/directories either by direct naming
* or by wildcard patterns.
* TODO Read list of entries to extract from FileList if its set.
*/
private void extract() throws IOException {
TarEntry entry;
InputStream in = null;
OutputStream out;
TarInputStream tin;
File file;
if (archive != null) {
if ((in = openFileRead(archive)) == null) {
fatal(" ", 1);
}
} else {
in = stdin;
}
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
if (use_stdout) {
out = stdout;
}
while ((entry = tin.getNextEntry()) != null) {
notice(entry.getName());
file = new File(entry.getName());
if (entry.isDirectory()) {
if (!file.exists()) {
file.mkdirs();
}
continue;
} else {
if (file.exists()) {
if (keepOld || (keepNew && (file.lastModified() >= entry.getModTime().getTime()))) {
continue;
}
if (backup) {
file.renameTo(new File(file.getPath() + suffix));
}
}
}
if ((out = openFileWrite(file, true, true)) == null) {
continue;
}
tin.copyEntryContents(out);
out.close();
}
tin.close();
}
/**
* List the contents of an archive.
*
* TODO Need to parse Path for choosing specific files/directories either by direct naming
* or by wildcard patterns.
*/
private void list() throws IOException {
TarEntry entry;
InputStream in = null;
TarInputStream tin;
if ((in = openFileRead(archive)) == null) {
fatal(" ", 1);
}
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
while ((entry = tin.getNextEntry()) != null) {
out(entry.getName());
}
}
/**
* Outputs the differences found between the archive and the file system.
*/
private void diff() throws IOException {
TarEntry entry;
InputStream in = null;
TarInputStream tin;
File file;
if ((in = openFileRead(archive)) == null) {
exit(1);
}
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
while ((entry = tin.getNextEntry()) != null) {
file = new File(entry.getName());
if (!file.exists()) {
out(file + ": Warning: No such file or directory");
continue;
}
if (file.lastModified() != entry.getModTime().getTime()) {
out(file + ": Mod time is different");
}
if (file.length() != entry.getSize()) {
out(file + ": Size is different");
}
// TODO check file mode
// TODO check file ownership
}
}
/**
* Copies an archive to another archive.
*
* This is used to set an output stream into position for appending new entries,
* and to copy entries from another archive into another archive.
*
* FIXME does not verify that tin is actually a tar archive (Concat)
*/
private void copy(TarInputStream tin, TarOutputStream tout) throws IOException {
TarEntry entry;
while ((entry = tin.getNextEntry()) != null) {
tout.putNextEntry(entry);
tin.copyEntryContents(tout);
tout.closeEntry();
}
tin.close();
}
/**
* Sets up a TarOutputStream suitable for appending new entries.
*/
private TarOutputStream appendTarOutputStream() throws IOException {
// FIXME this isnt working.
OutputStream out;
InputStream in;
TarOutputStream tout;
TarInputStream tin;
File tmpArchive;
tmpArchive = archive.getAbsoluteFile();
tmpArchive.renameTo(new File(archive.getName() + ".tmp"));
createArchive();
if ((out = openFileWrite(archive, false, false)) == null) {
fatal(" ", 1);
}
if (compress != 0) {
out = wrapOutputStream(out);
}
tout = new TarOutputStream(out);
if ((in = openFileRead(tmpArchive)) == null) {
fatal(" ", 1);
}
if (decompress != 0) {
in = wrapInputStream(in);
}
tin = new TarInputStream(in);
copy(tin, tout);
tmpArchive.delete();
return tout;
}
/**
* Creates a file for an archive, deleting it first if it already exists.
*/
private void createArchive() {
try {
if (archive.exists()) {
archive.delete();
}
if (!archive.createNewFile()) {
throw new IOException();
}
} catch (IOException e) {
fatal(err_file_create + archive, 1);
}
}
/**
* Processes a list of files and directories given on the command line
* in order to generate a list of files for Append, Create and Update.
*
* TODO Add parsing of FileList and exclusion filtering.
*/
private File[] processFiles(File[] files , boolean recurse) {
// FIXME object pollution
ArrayList<File> _files = new ArrayList<File>();
for (File file : files) {
if (!file.exists()) {
continue;
}
if (file.getName().equals(".") || file.getName().equals("..")) {
continue;
}
if (file.isDirectory()) {
if (recurse) {
_files.add(file);
Collections.addAll(_files, processFiles(file.listFiles(), recurse));
}
continue;
}
_files.add(file);
}
return _files.toArray(files);
}
// TODO Need to check that the list of files are actually archives or compressed archives.
private File[] processArchives(File[] files) {
return files;
}
/**
* Wraps an InputStream with a decompression stream for reading compressed archives.
*/
private InputStream wrapInputStream(InputStream in) throws IOException {
if (decompress == USE_BZIP) {
return new CBZip2InputStream(in);
}
if (decompress == USE_GZIP) {
return new GZIPInputStream(in, BUFFER_SIZE);
}
fatal("Internal Error: Unknown compress type", -1);
return null;
}
/**
* Wraps an OutputStream with a compression stream for writing compressed archives.
*/
private OutputStream wrapOutputStream(OutputStream out) throws IOException {
if (compress == USE_BZIP) {
return new CBZip2OutputStream(out);
}
if (compress == USE_GZIP) {
return new GZIPOutputStream(out);
}
fatal("Internal Error: Unknown decompress type", -1);
return null;
}
/**
* Used by create to determine if the archive should be compressed even if the -j or -z flags
* were not given.
*/
private int checkSuffix(File file) {
String name = file.getName();
if (name.endsWith(".tbz") || name.endsWith(".tbz2") || name.endsWith(".tar.bz") || name.endsWith(".tar.bz2")) {
return USE_BZIP;
}
if (name.endsWith(".tar.gz")) {
return USE_GZIP;
}
return 0;
}
/**
* Check via the file header the type of compression used to create it.
*/
private int checkCompressed(File file) throws IOException {
if (bzip) {
return checkBZipMagic(file) ? USE_BZIP : -1;
}
if (gzip) {
return checkGZipMagic(file) ? USE_GZIP : -1;
}
/*
if (checkBZipMagic(file)) {
return USE_BZIP;
}
if (checkGZipMagic(file)) {
return USE_GZIP;
}
*/
return 0;
}
// TODO
private boolean checkBZipMagic(File file) throws IOException {
return true;
}
// TODO
private boolean checkGZipMagic(File file) throws IOException {
return true;
}
/**
* Checks which operational mode was selected.
*
* If no mode was selected, or more than one mode was selected, the return
* value will be false, otherwise the return value is true, and this.mode will
* be set with the selected mode.
*/
private boolean checkMode() {
int check = 0;
if (DoAppend.isSet()) {
mode = TAR_APPEND;
check++;
}
if (DoCreate.isSet()) {
mode = TAR_CREATE;
check++;
}
if (DoConcat.isSet()) {
mode = TAR_CONCAT;
check++;
}
if (DoDelete.isSet()) {
mode = TAR_DELETE;
check++;
}
if (DoDiff.isSet()) {
mode = TAR_DIFF;
check++;
}
if (DoExtract.isSet()) {
mode = TAR_EXTRACT;
check++;
}
if (DoList.isSet()) {
mode = TAR_LIST;
check++;
}
if (DoUpdate.isSet()) {
mode = TAR_UPDATE;
check++;
}
return check == 1;
}
}