package co.codewizards.cloudstore.updater; import static co.codewizards.cloudstore.core.io.StreamUtil.*; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.Deflater; 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 org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.compress.compressors.gzip.GzipParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.oio.File; import co.codewizards.cloudstore.core.util.AssertUtil; public class TarGzFile { private static final Logger logger = LoggerFactory.getLogger(TarGzFile.class); private final File tarGzFile; private TarGzEntryNameConverter tarGzEntryNameConverter; private FileFilter fileFilter; public TarGzFile(final File tarGzFile) { this.tarGzFile = AssertUtil.assertNotNull(tarGzFile, "tarGzFile"); } /** * Gets the {@link FileFilter} deciding whether to process a file or not. * @return the {@link FileFilter} deciding whether to process a file or not. May be <code>null</code>. * If there is none, all files are processed. */ public FileFilter getFileFilter() { return fileFilter; } public void setFileFilter(final FileFilter fileFilter) { this.fileFilter = fileFilter; } public TarGzFile fileFilter(final FileFilter fileFilter) { setFileFilter(fileFilter); return this; } public TarGzEntryNameConverter getTarGzEntryNameConverter() { return tarGzEntryNameConverter; } public void setTarGzEntryNameConverter(final TarGzEntryNameConverter tarGzEntryNameConverter) { this.tarGzEntryNameConverter = tarGzEntryNameConverter; } public TarGzFile tarGzEntryNameConverter(final TarGzEntryNameConverter tarGzEntryNameConverter) { setTarGzEntryNameConverter(tarGzEntryNameConverter); return this; } public void compress(final File rootDir) throws IOException { boolean deleteIncompleteTarGzFile = false; final OutputStream fout = castStream(tarGzFile.createOutputStream()); try { deleteIncompleteTarGzFile = true; final GzipParameters gzipParameters = new GzipParameters(); gzipParameters.setCompressionLevel(Deflater.BEST_COMPRESSION); final TarArchiveOutputStream out = new TarArchiveOutputStream(new GzipCompressorOutputStream(new BufferedOutputStream(fout), gzipParameters)); try { writeTar(out, rootDir, rootDir); } finally { out.close(); } deleteIncompleteTarGzFile = false; } finally { fout.close(); if (deleteIncompleteTarGzFile) tarGzFile.delete(); } } private static final TarGzEntryNameConverter defaultEntryNameConverter = new DefaultTarGzEntryNameConverter(); private void writeTar(final TarArchiveOutputStream out, final File rootDir, final File dir) throws IOException { final TarGzEntryNameConverter tarGzEntryNameConverter = this.tarGzEntryNameConverter == null ? defaultEntryNameConverter : this.tarGzEntryNameConverter; try { final File[] children = dir.listFiles(fileFilter); if (children != null) { for (final File child : children) { final String entryName = tarGzEntryNameConverter.getEntryName(rootDir, child); final TarArchiveEntry archiveEntry = (TarArchiveEntry) out.createArchiveEntry(child.getIoFile(), entryName); if (child.canExecute()) archiveEntry.setMode(archiveEntry.getMode() | 0111); out.putArchiveEntry(archiveEntry); try { if (child.isFile()) { final InputStream in = castStream(child.createInputStream()); try { transferStreamData(in, out); } finally { in.close(); } } } finally { out.closeArchiveEntry(); } if (child.isDirectory()) writeTar(out, rootDir, child); } } } catch (IOException | RuntimeException x) { logger.error(x.toString(), x); throw x; } } public void extract(final File rootDir) throws IOException { rootDir.mkdirs(); final TarGzEntryNameConverter tarGzEntryNameConverter = this.tarGzEntryNameConverter == null ? defaultEntryNameConverter : this.tarGzEntryNameConverter; final FileFilter fileFilter = this.fileFilter; final InputStream fin = castStream(tarGzFile.createInputStream()); try { final TarArchiveInputStream in = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(fin))); try { TarArchiveEntry entry; while (null != (entry = in.getNextTarEntry())) { if(entry.isDirectory()) { // create the directory final File dir = tarGzEntryNameConverter.getFile(rootDir, entry.getName()); if (fileFilter == null || fileFilter.accept(dir.getIoFile())) { if (!dir.exists() && !dir.mkdirs()) throw new IllegalStateException("Could not create directory entry, possibly permission issues: " + dir.getAbsolutePath()); } } else { final File file = tarGzEntryNameConverter.getFile(rootDir, entry.getName()); if (fileFilter == null || fileFilter.accept(file.getIoFile())) { final File dir = file.getParentFile(); if (!dir.isDirectory()) dir.mkdirs(); // If the file already exists, we delete it and write into a new one - if possible. // This has the advantage (in GNU/Linux) if (file.isFile()) file.delete(); final OutputStream out = castStream(file.createOutputStream()); try { transferStreamData(in, out); } finally { out.close(); } if ((entry.getMode() & 0100) != 0 || (entry.getMode() & 010) != 0 || (entry.getMode() & 01) != 0) file.setExecutable(true, false); } } } } finally { in.close(); } } finally { fin.close(); } } private void transferStreamData(final InputStream in, final OutputStream out) throws IOException { int len; final byte[] buf = new byte[1024 * 16]; while( (len = in.read(buf)) > 0 ) { if (len > 0) out.write(buf, 0, len); } } }