/* * JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jef.tools; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import jef.common.BigDataBuffer; import jef.common.log.LogUtil; import jef.tools.support.ArchiveSummary; import jef.tools.zip.TarEntry; import jef.tools.zip.TarInputStream; import jef.tools.zip.TarOutputStream; import jef.tools.zip.VolSwitchAbleOutputStream; import jef.tools.zip.VolumnChangeableInputStream; import jef.tools.zip.VolumnOutputStream; import jef.tools.zip.ZipInputStream; import jef.tools.zip.ZipOutputStream; /** * JEF压缩解压的通用包:目前支持以下格式的文件 * <ul> * <li>zip 压缩/解压 密码不支持,修复了JDK的编码问题。</li> * <li>tar.gz 压缩/解压 修复了Apache同名类的编码问题。</li> * <li>tar 压缩/解压</li> * </ul> */ public class ZipUtils { static { ZipOutputStream.DEFAULT_NAME_ENCODING = "GB18030"; TarEntry.DEFAULT_NAME_ENCODING="GB18030"; } public static int TarLongFileNameMode=0; /** * 压缩为zip文件 * * @param zipFileName * 压缩包路径 * @param inputName * 源路径 * @throws IOException */ public static File zip(String zipFileName, String inputName) throws IOException { return zip(new File(zipFileName), new File(inputName)); } /** * 压缩为zip文件 * * @param zipFile 压缩包文件 * @param inputFiles * 多个压缩源 * @throws IOException */ public static File zip(File zipFile, File... inputFiles) throws IOException { VolumnOutputStream vol = new VolumnOutputStream(new VolSwitchAbleOutputStream(zipFile,0)); ZipOutputStream out=new ZipOutputStream(vol); for (File f : inputFiles) { zip(out, f, null, null); } out.flush(); out.close(); return vol.getFirstVolFile(); } /** * 压缩为zip文件 * @param zipFile 压缩包文件 * @param ep 压缩处理回调 * @param inputFiles 压缩源文件 * @throws IOException */ public static File zip(File zipFile, EntryProcessor ep, File... inputFiles) throws IOException { long size=ep==null?0:ep.getVolumnSize(); VolumnOutputStream vol = new VolumnOutputStream(new VolSwitchAbleOutputStream(zipFile,size)); ZipOutputStream out=new ZipOutputStream(vol); for (File f : inputFiles) { zip(out, f, null, ep); } out.flush(); out.close(); return vol.getFirstVolFile(); } /* * 递归压缩方法 * * @param out 压缩包输出流 * * @param f 需要压缩的文件 * * @param base压缩包中的路径 */ private static void zip(ZipOutputStream out, File f, String base, EntryProcessor ep) throws IOException { Assert.exist(f); if (StringUtils.isNotEmpty(base) && !base.endsWith("/")) base = base.concat("/"); if (f.isDirectory()) { base = StringUtils.toString(base) + f.getName() + "/"; base = (ep == null) ? base : ep.getZippedPath(f, base); if (base != null) { out.putNextEntry(new ZipEntry(base)); for (File fl : f.listFiles()) { zip(out, fl, base, ep); if (ep!=null && ep.breakProcess()) break; } } } else { // 如果是文件,则压缩 String entryName = StringUtils.toString(base) + f.getName(); entryName = ep == null ? entryName : ep.getZippedPath(f, entryName); if (entryName != null) { LogUtil.debug("adding to Zip:" + entryName); out.putNextEntry(new ZipEntry(entryName)); // 生成下一个压缩节点 FileInputStream in = new FileInputStream(f); // 读取文件内容 IOUtils.copy(in, out, false); in.close(); } } } /** * 解压zip文件 * * @param zipFile * 压缩包 * @param unzipPath * 解压路径 * @return */ public static boolean unzip(String zipFile, String unzipPath) { return unzip(new File(zipFile), unzipPath, null); } /** * 解压zip文件 * * @param file * 压缩包 * @param unzipPath * 解压路径 * @throws IOException */ public static boolean unzip(File file, String unzipPath, EntryProcessor cd) { InputStream in = null; try { in = new BufferedInputStream(new VolumnChangeableInputStream(file)); unzip(in, unzipPath, null, cd); return true; } catch (IOException e) { LogUtil.exception(e); return false; } finally { IOUtils.closeQuietly(in); } } /** * 单个文件Gzip压缩 * @param source 未压缩文件 * @param target 压缩文件 * @throws IOException * @author Administrator */ public static File gzip(File source,File targetFile,long volumnSize) throws IOException { VolumnOutputStream vol=new VolumnOutputStream(new VolSwitchAbleOutputStream(targetFile, volumnSize)); GZIPOutputStream target=new GZIPOutputStream(vol); IOUtils.copy(IOUtils.getInputStream(source), target, true); return vol.getFirstVolFile(); } /** * 单个文件Gzip解压缩 * @param source 压缩文件 * @param target 解压后文件 * @throws IOException */ public static void ungzip(File source,File target) throws IOException { IOUtils.copy(new GZIPInputStream(IOUtils.getInputStream(source)), IOUtils.getOutputStream(target), true); } /** * 解压 * * @param file * 压缩包 * @param unzipPath * 解压路径 * @param charSet * 压缩包内的文件名编码(可为null) * @param cd * 压缩处理器,压缩包的每个文件名都可以经过该类的检查和修正。(可以null) * @throws IOException */ public static void unzip(InputStream ins, String unzipPath, String charSet, EntryProcessor cd) throws IOException { ZipInputStream in = null; try { in = new ZipInputStream(ins, charSet); ZipEntry fEntry = null; while ((fEntry = in.getNextEntry()) != null) { String entryName = fEntry.getName(); if (cd != null) { entryName = cd.getExtractName(entryName, fEntry.getCompressedSize(), fEntry.getSize()); } if (entryName != null) { String fname = unzipPath + "/" + entryName; if (fname.endsWith("/")) { IOUtils.createFolder(fname); continue; } byte[] doc = new byte[1024]; File output = new File(fname); if (!output.getParentFile().exists()) { output.getParentFile().mkdirs(); } FileOutputStream out = new FileOutputStream(fname); int n; while ((n = in.read(doc, 0, 1024)) != -1) out.write(doc, 0, n); out.close(); out = null; doc = null; } } } finally { if (in != null) in.close(); // 关闭输入流 } } /** * 压缩为tar.gz格式文件 * * @param source * @param tarFilename * @throws IOException */ public static File targz(String tarFilename, String source) throws IOException { return targz(new File(source), new File(tarFilename)); } /** * 压缩为tar.gz格式的文件 * * @param source * @param tarFilename * @throws IOException */ public static File targz(File zipFile, File... source) throws IOException { return targz(zipFile,null,source); } /** * 打包成targz文件 * @param zipFile * @param ep * @param inputFiles * @throws IOException * @return file 返回压缩成功后的压缩文件(如果是分卷压缩返回第一个分卷文件,如果压缩不成功返回null) */ public static File targz(File zipFile, EntryProcessor ep, File... inputFiles) throws IOException { if(inputFiles==null || inputFiles.length==0){ return null; } BigDataBuffer bf=new BigDataBuffer(); TarOutputStream tarout=new TarOutputStream(bf); tarout.setLongFileMode(TarLongFileNameMode); try { for(File file: inputFiles){ tar(tarout,file,"",ep); } } catch (IOException e) { throw e; } finally { tarout.flush(); tarout.close(); } long size=ep==null?0:ep.getVolumnSize(); VolumnOutputStream vol=new VolumnOutputStream(new VolSwitchAbleOutputStream(zipFile, size)); GZIPOutputStream out=new GZIPOutputStream(vol); IOUtils.copy(bf.getAsStream(), out, true); bf.close(); // System.out.println(vol.getTotal()); return vol.getFirstVolFile(); } /** * 压缩tar格式的压缩文件 * * @param inputFilename * 压缩文件 * @param tarFilename * 输出路径 * @throws IOException */ public static File tar(String inputFilename, String tarFilename) throws IOException { return tar(new File(inputFilename), new File(tarFilename)); } /** * 压缩tar格式的压缩文件 * * @param inputFile * 压缩文件 * @param tarFilename * 输出路径 * @throws IOException */ public static File tar(File tarFilename,File... inputFile) throws IOException { return tar(tarFilename,null,inputFile); } /** * 压缩tar格式的压缩文件 * @param tarFile * @param ep * @param inputFile * @throws IOException */ public static File tar(File tarFile,EntryProcessor ep,File... inputFile) throws IOException { TarOutputStream out = new TarOutputStream(new FileOutputStream(tarFile)); out.setLongFileMode(TarLongFileNameMode); try { for(File input: inputFile){ tar(out,input,"",ep); } } catch (IOException e) { throw e; } finally { out.flush(); out.finish(); out.close(); } return tarFile; } /** * 将多个文件以Tar格式打包后 放入一个缓冲区内 * @param ep * @param inputFile * @return * @throws IOException */ public static BigDataBuffer tarBuffer(EntryProcessor ep,File... inputFile) throws IOException { BigDataBuffer result=new BigDataBuffer(); TarOutputStream out=new TarOutputStream(result); out.setLongFileMode(TarLongFileNameMode); try { for(File input: inputFile){ tar(out,input,"",ep); } } catch (IOException e) { throw e; } finally { out.flush(); out.finish(); out.close(); } return result; } /** * 压缩tar格式的压缩文件 * * @param f * 压缩文件 * @param out * 输出文件 * @param base * 结束标识 * @throws IOException */ private static void tar(TarOutputStream out, File f,String base,EntryProcessor ep) throws IOException { Assert.exist(f); if (StringUtils.isNotEmpty(base) && !base.endsWith("/")) base = base.concat("/"); if (f.isDirectory()) { base = StringUtils.toString(base) + f.getName() + "/"; base = (ep == null) ? base : ep.getZippedPath(f, base); if (base != null) { // LogUtil.debug("folder to Tar:" + base); out.putNextEntry(new TarEntry(base)); for (File file : f.listFiles()) { tar(out, file, base, ep); if (ep!=null && ep.breakProcess()) break; } out.closeEntry(); } } else { String entryName = StringUtils.toString(base) + f.getName(); entryName = ep == null ? entryName : ep.getZippedPath(f, entryName); if (entryName != null) { TarEntry entry=new TarEntry(entryName); entry.setSize(f.length()); // LogUtil.debug("adding to Tar:" + entryName+" "+f.length()); out.putNextEntry(entry); // 生成下一个压缩节点 FileInputStream in = new FileInputStream(f); // IOUtils.copy(in, out, false); in.close(); out.closeEntry(); } } } /** * 解压tar.gz压缩包 * * @param archivePath * 压缩包路径 * @param unzipPath * 解压路径 * @throws IOException */ public static boolean unTarGz(String archivePath, String unzipPath) { return unTarGz(new File(archivePath), unzipPath, null); } /** * 解压tar.gz格式的压缩包 * * @param in * @param unzipPath * @param cd * @throws IOException */ public static boolean unTarGz(File file, String unzipPath, EntryProcessor cd) { try { InputStream in = new BufferedInputStream(new VolumnChangeableInputStream(file)); return unTarGz(in, unzipPath, cd); } catch (IOException e) { LogUtil.exception(e); return false; } } /** * 解压tar.gz格式的输入流 * * @param in * @param unzipPath * @param cd * @throws IOException */ public static boolean unTarGz(InputStream in, String unzipPath, EntryProcessor cd) { try { untar(new GZIPInputStream(in), unzipPath, cd); return true; } catch (IOException e) { LogUtil.exception(e); return false; } } /** * 解压tar格式的压缩文件 * @param archivePath * @param unzipPath * @return */ public static boolean untar(String archivePath, String unzipPath) { return untar(new File(archivePath), unzipPath, null); } /** * 解压tar格式的压缩文件到指定目录下 * * @param tarFileName * 压缩文件 * @param extPlace * 解压目录 * @throws Exception */ public static boolean untar(File file, String unzipPath, EntryProcessor cd) { try { untar(new BufferedInputStream(new VolumnChangeableInputStream(file)), unzipPath, cd); return true; } catch (IOException e) { LogUtil.exception(e); return false; } } /** * 解压tar格式的文件 * * @param tarStream * @param unzipPath * @param cd * @throws IOException */ public static void untar(InputStream tarStream, String unzipPath, EntryProcessor cd) throws IOException { TarInputStream in = null; try { in = new TarInputStream(tarStream); TarEntry fEntry = null; while ((fEntry = in.getNextEntry()) != null) { String entryName = fEntry.getName(); // LogUtil.debug("untar:"+ entryName+" "+ fEntry.getSize()); if (cd != null) { entryName = cd.getExtractName(entryName, fEntry.getSize(), fEntry.getSize()); } if (entryName != null) { String fname = unzipPath + "/" + entryName; if (fEntry.isDirectory()) { IOUtils.createFolder(fname); continue; }else{ OutputStream out = IOUtils.getOutputStream(new File(fname)); @SuppressWarnings("unused") long size=IOUtils.copy(in, out, false,true); // LogUtil.debug("size="+size); } }else{ long size=in.skip(fEntry.getSize()); if(size>0)LogUtil.debug("left:" + size); } if (cd != null && cd.breakProcess()) break; } } finally { if (in != null) in.close(); // 关闭输入流 } } /** * 得到tar压缩文件的摘要信息 * @param file * @return */ public static ArchiveSummary getTarSummary(File file) { SummaryCollector sc = new SummaryCollector(); untar(file, null, sc); return sc.getSummary(); } /** * 得到zip压缩文件的摘要信息 * @param file * @return */ public static ArchiveSummary getZipArchiveSummary(File file) { SummaryCollector sc = new SummaryCollector(); unzip(file, null, sc); return sc.getSummary(); } /** * 得到targz压缩文件的摘要信息 * @param file * @return */ public static ArchiveSummary getTarGzSummary(File file) { SummaryCollector sc = new SummaryCollector(); unTarGz(file, null, sc); return sc.getSummary(); } /** * 默认的EntryProcessor实现B,目的收集压缩包的各项信息 * @author Administrator * */ public static class SummaryCollector extends EntryProcessor { private ArchiveSummary summary; public SummaryCollector() { summary = new ArchiveSummary(); } public boolean breakProcess() { return false; } public String getExtractName(String input, long packedSize, long unpackedSize) { summary.addItem(input, packedSize, unpackedSize); return null; } public ArchiveSummary getSummary() { return summary; } } /** * 默认的EntryProcessor实现B,目的是在控制台上打印出压缩解压进度 * @author Administrator * */ public static class ConsoleShow extends EntryProcessor { private ArchiveSummary summary; private long currentPosition = 0; private long nextPromptSize = 0;// 下次提示 private int count = 0; private long step; // 每次提示的步长 private String name; public ConsoleShow(String name, ArchiveSummary size) { this.name = name; this.summary = size; this.step = size.getPackedSize() / 10; if (step < 100) step = 100; } public boolean breakProcess() { return false; } public String getExtractName(String input, long ps, long unp) { count++; if (currentPosition >= nextPromptSize) { nextPromptSize += step; String percent = count + "/" + summary.getItemCount() + " " + StringUtils.toPercent(currentPosition, summary.getPackedSize()); LogUtil.debug(name.replace("%%", percent).replace("$$", input)); } currentPosition += ps; return input.replace('?', '_'); } public String getZippedPath(File source, String zippedBase) { return null; } } public static ConsoleShow getConsoleProgressHandler(String msg, ArchiveSummary summary) { return new ConsoleShow(msg, summary); } /** * 压缩处理器 * 抽象类,你可以覆盖整个类的各种方法,实现以下功能 * 1、指定分卷压缩的大小 * 2、指定压缩后文件的名称、指定解压后文件的路径 * 3、指定某些文件跳过不压缩或者不解压 * 4、通过覆盖对应的事件,可以计算压缩解压的进度、时间、字节数等 * 当需要定制上述特殊行为时,可以传入一个处理器,实现你需要的逻辑 * @author Administrator * @Date 2011-7-7 */ public abstract static class EntryProcessor { /** * 当一个文件将被解压前调用,返回压缩后的文件路径 * @param entryName 压缩包中的文件路径 * @param packedSize 文件压缩后大小 * @param unpackedSize 压缩前大小 * @return 解压后文件(相对)路径,默认应当和entryName一致。 * 如果不想解压此文件,可以return null. */ protected String getExtractName(String entryName, long packedSize, long unpackedSize) { return entryName; } /** * 返回分卷大小 * 如果返回0表示无须分卷 */ protected long getVolumnSize(){ return 0; } /** * 当一个文件将被压缩前调用,返回文件在压缩包中的路径 * @param source 源文件 * @param zippedPath 默认压缩包中的文件路径 * @return 默认压缩包中的文件路径,如果不想压缩此文件,返回null. */ protected String getZippedPath(File source, String zippedPath) { return zippedPath; } /** * 当每个文件压缩/解压后执行 * @return true,继续压缩/解压任务。false则中断整个操作 */ protected boolean breakProcess() { return false; } } }