package org.rr.commons.utils.compression.truezip; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.rr.commons.log.LoggerFactory; import org.rr.commons.mufs.IResourceHandler; import org.rr.commons.utils.compression.CompressedDataEntry; import org.rr.commons.utils.compression.CompressionUtils; import org.rr.commons.utils.compression.EmptyFileEntryFilter; import org.rr.commons.utils.compression.FileEntryFilter; import de.schlichtherle.truezip.file.TArchiveDetector; import de.schlichtherle.truezip.file.TConfig; import de.schlichtherle.truezip.file.TFile; import de.schlichtherle.truezip.file.TFileOutputStream; import de.schlichtherle.truezip.file.TVFS; import de.schlichtherle.truezip.fs.FsOutputOption; import de.schlichtherle.truezip.fs.FsSyncException; import de.schlichtherle.truezip.fs.archive.zip.JarDriver; import de.schlichtherle.truezip.socket.sl.IOPoolLocator; public class TrueZipUtils { static { TConfig.get().setArchiveDetector( new TArchiveDetector( "cbz|epub", new JarDriver(IOPoolLocator.SINGLETON))); } /** * Extracts all entries from a zip file that are accepted by the given {@link ZipFileFilter}. * @param zipFile The zip file in the file system. * @param path qualified zip path of the entry to be extracted. * @return The desired extracted entries. */ public static List<CompressedDataEntry> extract(IResourceHandler zipFileHandler, FileEntryFilter filter) { final List<CompressedDataEntry> result = new ArrayList<>(); final List<String> entryList = list(zipFileHandler); for(String entry : entryList) { if(filter == null || filter.accept(entry, entry.getBytes())) { result.add(extract(zipFileHandler, entry)); } } return result; } /** * Extracts one entry from a zip file. * @param zipFile The zip file in the file system. * @param name qualified zip path of the entry to be extracted. * @return The desired extracted entry. Never returns <code>null</code> but the * result {@link TrueZipDataEntry} would return no bytes if the zip entry did not exists. */ public static CompressedDataEntry extract(IResourceHandler zipFileHandler, String name) { return new LazyTrueZipDataEntry(zipFileHandler, name); } /** * List all entries of the zip file. */ public static List<String> list(IResourceHandler zipFileHandler) { return list(zipFileHandler, new EmptyFileEntryFilter()); } /** * List all entries of the zip file allowed by the given {@link ZipFileFilter} instance. */ public static List<String> list(IResourceHandler zipFileHandler, FileEntryFilter zipFileFilter) { File zipFile = zipFileHandler.toFile(); TFile archive = new TFile(zipFile.toString()); Collection<File> listFiles = FileUtils.listFiles(archive, null, true); ArrayList<String> result = new ArrayList<>(); for(File f : listFiles) { String enclEntryName = ((TFile)f).getEnclEntryName(); if(zipFileFilter.accept(enclEntryName, enclEntryName.getBytes())) { result.add(enclEntryName); } } Collections.sort(result); unmout(); return result; } /** * Add / Replace the an entry with the given name and given data */ public static boolean add(IResourceHandler zipFileHandler, String name, InputStream data) { File zipFile = zipFileHandler.toFile(); boolean isDeleted; // First, push a new current configuration on the inheritable thread local // stack. TConfig config = TConfig.push(); try { // Set FsOutputOption.GROW for appending-to rather than reassembling an // archive file. if(zipFile.length() > 5000000) { //Larger than five MB - don't reassemble the zip file. config.setOutputPreferences(config.getOutputPreferences().set(FsOutputOption.GROW)); } if(CompressionUtils.isStoreOnlyFile(name)) { config.setOutputPreferences(config.getOutputPreferences().set(FsOutputOption.STORE)); } TFile file = new TFile(zipFile.toString() + "/" + name); // Now use the current configuration and append the entry to the archive // file even if it's already present. TFileOutputStream zipOut = new TFileOutputStream(file); try { IOUtils.copy(data, zipOut); } finally { zipOut.flush(); IOUtils.closeQuietly(zipOut); } isDeleted = true; } catch(Exception e) { isDeleted = false; } finally { // Pop the current configuration off the inheritable thread local stack. config.close(); unmout(); } return isDeleted; } /** * Threading problem makes it indispensable to invoke unmount manually * from the parent thread. */ public static void unmout() { try { TVFS.umount();//commit changes } catch (FsSyncException e) { LoggerFactory.getLogger().log(Level.SEVERE, "Unmount failed", e); } } public static ZipOutputStream createZipOutputStream(OutputStream out) { ZipOutputStream zout = new ZipOutputStream(out); return zout; } }