blob: cc184134640fa8d7a8cc328dc5d1810e5a03f9ec [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 com.android.tradefed.util;
import com.android.tradefed.log.LogUtil.CLog;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* A helper class for compression-related operations
*/
public class ZipUtil {
/**
* Utility method to verify that a zip file is not corrupt.
*
* @param zipFile the {@link File} to check
* @param thorough Whether to attempt to fully extract the archive. If {@code false}, this
* method will fail to detect CRC errors in a well-formed archive.
* @throws IOException if the file could not be opened or read
* @return {@code false} if the file appears to be corrupt; {@code true} otherwise
*/
public static boolean isZipFileValid(File zipFile, boolean thorough) throws IOException {
if (zipFile != null && !zipFile.exists()) {
CLog.d("Zip file does not exist: %s", zipFile.getAbsolutePath());
return false;
}
try {
final ZipFile z = new ZipFile(zipFile);
if (thorough) {
// Reading the entire file is the only way to detect CRC errors within the archive
final File extractDir = FileUtil.createTempDir("extract-" + zipFile.getName());
try {
extractZip(z, extractDir);
} finally {
FileUtil.recursiveDelete(extractDir);
}
}
} catch (ZipException e) {
// File is likely corrupt
CLog.d("Detected corrupt zip file %s: %s", zipFile.getCanonicalPath(), e.getMessage());
return false;
}
return true;
}
/**
* Utility method to extract entire contents of zip file into given directory
*
* @param zipFile the {@link ZipFile} to extract
* @param destDir the local dir to extract file to
* @throws IOException if failed to extract file
*/
public static void extractZip(ZipFile zipFile, File destDir) throws IOException {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File childFile = new File(destDir, entry.getName());
childFile.getParentFile().mkdirs();
if (entry.isDirectory()) {
continue;
} else {
FileUtil.writeToFile(zipFile.getInputStream(entry), childFile);
}
}
}
/**
* Utility method to extract one specific file from zip file into a tmp file
*
* @param zipFile the {@link ZipFile} to extract
* @param filePath the filePath of to extract
* @throws IOException if failed to extract file
* @return the {@link File} or null if not found
*/
public static File extractFileFromZip(ZipFile zipFile, String filePath) throws IOException {
ZipEntry entry = zipFile.getEntry(filePath);
if (entry == null) {
return null;
}
File createdFile = FileUtil.createTempFile("extracted",
FileUtil.getExtension(filePath));
FileUtil.writeToFile(zipFile.getInputStream(entry), createdFile);
return createdFile;
}
/**
* Utility method to create a temporary zip file containing the given directory and
* all its contents.
*
* @param dir the directory to zip
* @return a temporary zip {@link File} containing directory contents
* @throws IOException if failed to create zip file
*/
public static File createZip(File dir) throws IOException {
File zipFile = FileUtil.createTempFile("dir", ".zip");
createZip(dir, zipFile);
return zipFile;
}
/**
* Utility method to create a zip file containing the given directory and
* all its contents.
*
* @param dir the directory to zip
* @param zipFile the zip file to create - it should not already exist
* @throws IOException if failed to create zip file
*/
public static void createZip(File dir, File zipFile) throws IOException {
ZipOutputStream out = null;
try {
FileOutputStream fileStream = new FileOutputStream(zipFile);
out = new ZipOutputStream(new BufferedOutputStream(fileStream));
addToZip(out, dir, new LinkedList<String>());
} catch (IOException e) {
zipFile.delete();
throw e;
} catch (RuntimeException e) {
zipFile.delete();
throw e;
} finally {
StreamUtil.close(out);
}
}
/**
* Recursively adds given file and its contents to ZipOutputStream
*
* @param out the {@link ZipOutputStream}
* @param file the {@link File} to add to the stream
* @param relativePathSegs the relative path of file, including separators
* @throws IOException if failed to add file to zip
*/
public static void addToZip(ZipOutputStream out, File file, List<String> relativePathSegs)
throws IOException {
relativePathSegs.add(file.getName());
if (file.isDirectory()) {
// note: it appears even on windows, ZipEntry expects '/' as a path separator
relativePathSegs.add("/");
}
ZipEntry zipEntry = new ZipEntry(buildPath(relativePathSegs));
out.putNextEntry(zipEntry);
if (file.isFile()) {
writeToStream(file, out);
}
out.closeEntry();
if (file.isDirectory()) {
// recursively add contents
File[] subFiles = file.listFiles();
if (subFiles == null) {
throw new IOException(String.format("Could not read directory %s",
file.getAbsolutePath()));
}
for (File subFile : subFiles) {
addToZip(out, subFile, relativePathSegs);
}
// remove the path separator
relativePathSegs.remove(relativePathSegs.size()-1);
}
// remove the last segment, added at beginning of method
relativePathSegs.remove(relativePathSegs.size()-1);
}
/**
* Close an open {@link ZipFile}, ignoring any exceptions.
*
* @param zipFile the file to close
*/
public static void closeZip(ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException e) {
// ignore
}
}
}
/**
* Helper method to create a gzipped version of a single file.
*
* @param file the original file
* @param gzipFile the file to place compressed contents in
* @throws IOException
*/
public static void gzipFile(File file, File gzipFile) throws IOException {
GZIPOutputStream out = null;
try {
FileOutputStream fileStream = new FileOutputStream(gzipFile);
out = new GZIPOutputStream(new BufferedOutputStream(fileStream, 64 * 1024));
writeToStream(file, out);
} catch (IOException e) {
gzipFile.delete();
throw e;
} catch (RuntimeException e) {
gzipFile.delete();
throw e;
} finally {
StreamUtil.close(out);
}
}
/**
* Helper method to write input file contents to output stream.
*
* @param file the input {@link File}
* @param out the {@link OutputStream}
*
* @throws IOException
*/
private static void writeToStream(File file, OutputStream out) throws IOException {
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));
StreamUtil.copyStreams(inputStream, out);
} finally {
StreamUtil.close(inputStream);
}
}
/**
* Builds a file system path from a stack of relative path segments
*
* @param relativePathSegs the list of relative paths
* @return a {@link String} containing all relativePathSegs
*/
private static String buildPath(List<String> relativePathSegs) {
StringBuilder pathBuilder = new StringBuilder();
for (String segment : relativePathSegs) {
pathBuilder.append(segment);
}
return pathBuilder.toString();
}
/**
* Extract a zip file to a temp directory prepended with a string
*
* @param zipFile the zip file to extract
* @param nameHint a prefix for the temp directory
* @return a {@link File} pointing to the temp directory
*/
public static File extractZipToTemp(File zipFile, String nameHint)
throws IOException, ZipException {
File localRootDir = FileUtil.createTempDir(nameHint);
extractZip(new ZipFile(zipFile), localRootDir);
return localRootDir;
}
}