/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.commons.lang;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
/**
* @author andrew00x
*/
public class TarUtils {
private static final int BUF_SIZE = 4096;
/**
* Add content of directory {@code dir} to tar archive {@code tar}.
*
* @param parentPath
* parent path of tar archive. Typically if need add only content of {@code dir} this path should be absolute path to {@code
* dir} but if need to have in archive some parents this parameter may be used. For example if need add to archive content of
* directory '/a/b/c' but need to save directory 'c' in path:
* <pre>
* {@code File dir = new File("a/b/c");
* File tar = new File("archive.tar");
* TarUtils.tarDir(dir.getParentFile().getAbsolutePath(), dir, tar, -1, IoUtil.ANY_FILTER);
* }
* </pre>
* In this case directory 'c' is added in tar archive.
* @param dir
* dir to add
* @param tar
* tar archive
* @param modTime
* modification time that applied to all entries in archive instead modification time provided by method {@link
* File#lastModified()}. This parameter should be {@code -1} if don't need to set any specified time
* @param filter
* optional filter for files to add in archive
* @throws IOException
* if i/o error occurs
* @throws IllegalArgumentException
* if {@code dir} is not directory or if {@code parentPath} is invalid, e.g. is neither parent nor equals to path of {@code
* dir}
*/
public static void tarDir(String parentPath, File dir, File tar, long modTime, FilenameFilter filter) throws IOException {
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Not a directory.");
}
if (!dir.getAbsolutePath().startsWith(parentPath)) {
throw new IllegalArgumentException("Invalid parent directory path " + parentPath);
}
if (filter == null) {
filter = IoUtil.ANY_FILTER;
}
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(tar)))) {
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
addDirectoryRecursively(tarOut, parentPath, dir, modTime, filter);
}
}
public static void tarDir(String parentPath, File dir, File tar, FilenameFilter filter) throws IOException {
tarDir(parentPath, dir, tar, -1, filter);
}
public static void tarFiles(File tar, long modTime, File... files) throws IOException {
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(tar)))) {
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
for (File f : files) {
if (f.isDirectory()) {
addDirectoryEntry(tarOut, f.getName(), f, modTime);
final String parentPath = f.getParentFile().getAbsolutePath();
addDirectoryRecursively(tarOut, parentPath, f, modTime, IoUtil.ANY_FILTER);
} else if (f.isFile()) {
addFileEntry(tarOut, f.getName(), f, modTime);
}
}
}
}
public static void tarFiles(File tar, File... files) throws IOException {
tarFiles(tar, -1, files);
}
private static void addDirectoryRecursively(TarArchiveOutputStream tarOut,
String parentPath,
File dir,
long modTime,
FilenameFilter filter) throws IOException {
final int parentPathLength = parentPath.length() + 1;
final LinkedList<File> q = new LinkedList<>();
q.add(dir);
while (!q.isEmpty()) {
final File current = q.pop();
final File[] list = current.listFiles();
if (list != null) {
for (File f : list) {
if (filter.accept(current, f.getName())) {
final String entryName = f.getAbsolutePath().substring(parentPathLength).replace('\\', '/');
if (f.isDirectory()) {
addDirectoryEntry(tarOut, entryName, f, modTime);
q.push(f);
} else if (f.isFile()) {
addFileEntry(tarOut, entryName, f, modTime);
}
}
}
}
}
}
private static void addDirectoryEntry(TarArchiveOutputStream tarOut, String entryName, File directory, long modTime)
throws IOException {
final TarArchiveEntry tarEntry = new TarArchiveEntry(directory, entryName);
if (modTime >= 0) {
tarEntry.setModTime(modTime);
}
tarOut.putArchiveEntry(tarEntry);
tarOut.closeArchiveEntry();
}
private static void addFileEntry(TarArchiveOutputStream tarOut, String entryName, File file, long modTime) throws IOException {
final TarArchiveEntry tarEntry = new TarArchiveEntry(file, entryName);
if (modTime >= 0) {
tarEntry.setModTime(modTime);
}
tarOut.putArchiveEntry(tarEntry);
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
final byte[] buf = new byte[BUF_SIZE];
int r;
while ((r = in.read(buf)) != -1) {
tarOut.write(buf, 0, r);
}
}
tarOut.closeArchiveEntry();
}
public static void untar(File tar, File targetDir) throws IOException {
try (InputStream in = new FileInputStream(tar)) {
untar(in, targetDir);
}
}
public static void untar(InputStream in, File targetDir) throws IOException {
final TarArchiveInputStream tarIn = new TarArchiveInputStream(in);
byte[] b = new byte[BUF_SIZE];
TarArchiveEntry tarEntry;
while ((tarEntry = tarIn.getNextTarEntry()) != null) {
final File file = new File(targetDir, tarEntry.getName());
if (tarEntry.isDirectory()) {
if(!file.mkdirs()){
throw new IOException("Unable to create folder "+ file.getAbsolutePath());
}
} else {
final File parent = file.getParentFile();
if (!parent.exists()) {
if(!parent.mkdirs()){
throw new IOException("Unable to create folder "+ parent.getAbsolutePath());
}
}
try (FileOutputStream fos = new FileOutputStream(file)) {
int r;
while ((r = tarIn.read(b)) != -1) {
fos.write(b, 0, r);
}
}
}
}
}
public static boolean isTarFile(File file) throws IOException {
if (file.isDirectory()) {
return false;
}
// http://en.wikipedia.org/wiki/Tar_(computing)#File_header
final byte[] header = new byte[512];
try (FileInputStream fIn = new FileInputStream(file)) {
if (fIn.read(header) != header.length) {
return false;
}
}
return TarArchiveInputStream.matches(header, header.length);
}
private TarUtils() {
}
}