001package co.codewizards.cloudstore.updater;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004
005import java.io.BufferedInputStream;
006import java.io.BufferedOutputStream;
007import java.io.File;
008import java.io.FileFilter;
009import java.io.FileInputStream;
010import java.io.FileOutputStream;
011import java.io.IOException;
012import java.io.InputStream;
013import java.io.OutputStream;
014import java.util.zip.Deflater;
015
016import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
017import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
018import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
019import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
020import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
021import org.apache.commons.compress.compressors.gzip.GzipParameters;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025public class TarGzFile {
026        private static final Logger logger = LoggerFactory.getLogger(TarGzFile.class);
027
028        private final File tarGzFile;
029        private TarGzEntryNameConverter tarGzEntryNameConverter;
030        private FileFilter fileFilter;
031
032        public TarGzFile(final File tarGzFile) {
033                this.tarGzFile = assertNotNull("tarGzFile", tarGzFile);
034        }
035
036        /**
037         * Gets the {@link FileFilter} deciding whether to process a file or not.
038         * @return the {@link FileFilter} deciding whether to process a file or not. May be <code>null</code>.
039         * If there is none, all files are processed.
040         */
041        public FileFilter getFileFilter() {
042                return fileFilter;
043        }
044        public void setFileFilter(FileFilter fileFilter) {
045                this.fileFilter = fileFilter;
046        }
047        public TarGzFile fileFilter(FileFilter fileFilter) {
048                setFileFilter(fileFilter);
049                return this;
050        }
051
052        public TarGzEntryNameConverter getTarGzEntryNameConverter() {
053                return tarGzEntryNameConverter;
054        }
055        public void setTarGzEntryNameConverter(TarGzEntryNameConverter tarGzEntryNameConverter) {
056                this.tarGzEntryNameConverter = tarGzEntryNameConverter;
057        }
058        public TarGzFile tarGzEntryNameConverter(TarGzEntryNameConverter tarGzEntryNameConverter) {
059                setTarGzEntryNameConverter(tarGzEntryNameConverter);
060                return this;
061        }
062
063        public void compress(final File rootDir) throws IOException {
064                boolean deleteIncompleteTarGzFile = false;
065                final FileOutputStream fout = new FileOutputStream(tarGzFile);
066                try {
067                        deleteIncompleteTarGzFile = true;
068
069                        final GzipParameters gzipParameters = new GzipParameters();
070                        gzipParameters.setCompressionLevel(Deflater.BEST_COMPRESSION);
071                        final TarArchiveOutputStream out = new TarArchiveOutputStream(new GzipCompressorOutputStream(new BufferedOutputStream(fout), gzipParameters));
072                        try {
073                                writeTar(out, rootDir, rootDir);
074                        } finally {
075                                out.close();
076                        }
077                        deleteIncompleteTarGzFile = false;
078                } finally {
079                        fout.close();
080                        if (deleteIncompleteTarGzFile)
081                                tarGzFile.delete();
082                }
083        }
084
085        private static final TarGzEntryNameConverter defaultEntryNameConverter = new DefaultTarGzEntryNameConverter();
086
087        private void writeTar(final TarArchiveOutputStream out, final File rootDir, final File dir) throws IOException {
088                final TarGzEntryNameConverter tarGzEntryNameConverter = this.tarGzEntryNameConverter == null ? defaultEntryNameConverter : this.tarGzEntryNameConverter;
089                try {
090                        final File[] children = dir.listFiles(fileFilter);
091                        if (children != null) {
092                                for (final File child : children) {
093                                        final String entryName = tarGzEntryNameConverter.getEntryName(rootDir, child);
094                                        TarArchiveEntry archiveEntry = (TarArchiveEntry) out.createArchiveEntry(child, entryName);
095
096                                        if (child.canExecute())
097                                                archiveEntry.setMode(archiveEntry.getMode() | 0111);
098
099                                        out.putArchiveEntry(archiveEntry);
100                                        try {
101                                                if (child.isFile()) {
102                                                        final InputStream in = new FileInputStream(child);
103                                                        try {
104                                                                transferStreamData(in, out);
105                                                        } finally {
106                                                                in.close();
107                                                        }
108                                                }
109                                        } finally {
110                                                out.closeArchiveEntry();
111                                        }
112
113                                        if (child.isDirectory())
114                                                writeTar(out, rootDir, child);
115                                }
116                        }
117                } catch (IOException | RuntimeException x) {
118                        logger.error(x.toString(), x);
119                        throw x;
120                }
121        }
122
123        public void extract(final File rootDir) throws IOException {
124                rootDir.mkdirs();
125                final TarGzEntryNameConverter tarGzEntryNameConverter = this.tarGzEntryNameConverter == null ? defaultEntryNameConverter : this.tarGzEntryNameConverter;
126                final FileFilter fileFilter = this.fileFilter;
127                final FileInputStream fin = new FileInputStream(tarGzFile);
128                try {
129                        final TarArchiveInputStream in = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(fin)));
130                        try {
131                                TarArchiveEntry entry;
132                                while (null != (entry = in.getNextTarEntry())) {
133                                        if(entry.isDirectory()) {
134                                                // create the directory
135                                                final File dir = tarGzEntryNameConverter.getFile(rootDir, entry.getName());
136                                                if (fileFilter == null || fileFilter.accept(dir)) {
137                                                        if (!dir.exists() && !dir.mkdirs())
138                                                                throw new IllegalStateException("Could not create directory entry, possibly permission issues: " + dir.getAbsolutePath());
139                                                }
140                                        }
141                                        else {
142                                                final File file = tarGzEntryNameConverter.getFile(rootDir, entry.getName());
143                                                if (fileFilter == null || fileFilter.accept(file)) {
144                                                        final File dir = file.getParentFile();
145                                                        if (!dir.isDirectory())
146                                                                dir.mkdirs();
147
148                                                        // If the file already exists, we delete it and write into a new one - if possible.
149                                                        // This has the advantage (in GNU/Linux)
150                                                        if (file.isFile())
151                                                                file.delete();
152
153                                                        final OutputStream out = new FileOutputStream(file);
154                                                        try {
155                                                                transferStreamData(in, out);
156                                                        } finally {
157                                                                out.close();
158                                                        }
159
160                                                        if ((entry.getMode() & 0100) != 0 || (entry.getMode() & 010) != 0 || (entry.getMode() & 01) != 0)
161                                                                file.setExecutable(true, false);
162                                                }
163                                        }
164                                }
165                        } finally {
166                                in.close();
167                        }
168                } finally {
169                        fin.close();
170                }
171        }
172
173        private void transferStreamData(final InputStream in, final OutputStream out) throws IOException {
174                int len;
175                byte[] buf = new byte[1024 * 16];
176                while( (len = in.read(buf)) > 0 ) {
177                        if (len > 0)
178                                out.write(buf, 0, len);
179                }
180        }
181
182}