| /* |
| * Copyright 2012-present Facebook, Inc. |
| * |
| * 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.facebook.buck.zip; |
| |
| import com.facebook.buck.step.ExecutionContext; |
| import com.facebook.buck.step.Step; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.hash.Hashing; |
| import com.google.common.io.ByteStreams; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| /** |
| * A command that creates a copy of a ZIP archive, making sure that certain user-specified entries |
| * are packed with a certain compression level. |
| * |
| * Can be used, for instance, to force the resources.arsc file in an Android .apk to be compressed. |
| */ |
| public class RepackZipEntriesStep implements Step { |
| |
| private final String inputFile; |
| private final String outputFile; |
| private final ImmutableSet<String> entries; |
| private final int compressionLevel; |
| |
| /** |
| * Creates a {@link RepackZipEntriesStep}. A temporary directory will be created and used |
| * to extract entries. Entries will be packed with the maximum compression level. |
| * @param inputFile input archive |
| * @param outputFile destination archive |
| * @param entries files to repack (e.g. {@code ImmutableSet.of("resources.arsc")}) |
| */ |
| public RepackZipEntriesStep( |
| String inputFile, |
| String outputFile, |
| ImmutableSet<String> entries) { |
| this(inputFile, outputFile, entries, ZipStep.MAX_COMPRESSION_LEVEL); |
| } |
| |
| /** |
| * Creates a {@link RepackZipEntriesStep}. |
| * @param inputFile input archive |
| * @param outputFile destination archive |
| * @param entries files to repack (e.g. {@code ImmutableSet.of("resources.arsc")}) |
| * @param compressionLevel 0 to 9 |
| */ |
| public RepackZipEntriesStep( |
| String inputFile, |
| String outputFile, |
| ImmutableSet<String> entries, |
| int compressionLevel) { |
| this.inputFile = inputFile; |
| this.outputFile = outputFile; |
| this.entries = entries; |
| this.compressionLevel = compressionLevel; |
| } |
| |
| @Override |
| public int execute(ExecutionContext context) { |
| try ( |
| ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(inputFile))); |
| CustomZipOutputStream out = ZipOutputStreams.newOutputStream(new File(outputFile)); |
| ) { |
| for (ZipEntry entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { |
| CustomZipEntry customEntry = new CustomZipEntry(entry); |
| if (entries.contains(customEntry.getName())) { |
| customEntry.setCompressionLevel(compressionLevel); |
| } |
| |
| InputStream toUse; |
| // If we're using STORED files, we must pre-calculate the CRC. |
| if (customEntry.getMethod() == ZipEntry.STORED) { |
| try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { |
| ByteStreams.copy(in, bos); |
| byte[] bytes = bos.toByteArray(); |
| customEntry.setCrc(Hashing.crc32().hashBytes(bytes).padToLong()); |
| customEntry.setSize(bytes.length); |
| customEntry.setCompressedSize(bytes.length); |
| toUse = new ByteArrayInputStream(bytes); |
| } |
| } else { |
| toUse = in; |
| } |
| |
| out.putNextEntry(customEntry); |
| ByteStreams.copy(toUse, out); |
| out.closeEntry(); |
| } |
| |
| return 0; |
| } catch (IOException e) { |
| context.logError(e, "Unable to repack zip"); |
| return 1; |
| } |
| } |
| |
| @Override |
| public String getShortName() { |
| return "repack zip"; |
| } |
| |
| @Override |
| public String getDescription(ExecutionContext context) { |
| // We don't actually want to create a temp directory. Since this is for cut-and-paste joy only |
| // currentTimeMillis is sufficiently random. |
| Path temp = Paths.get(System.getProperty("java.io.tmpdir"), "repack" + System.currentTimeMillis()); |
| |
| return new StringBuilder("cd ").append(temp.toString()).append(" && ") |
| .append("unzip ").append(inputFile).append(" && ") |
| .append("zip -r -").append(compressionLevel).append(" ").append(outputFile).append(" *") |
| .toString(); |
| } |
| } |