/* * Copyright 2000-2015 JetBrains s.r.o. * * 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.intellij.platform.templates.github; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.NullableFunction; import com.intellij.util.Producer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.concurrent.Callable; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; /** * @author Sergey Simonchik */ public class ZipUtil { private static final Logger LOG = Logger.getInstance(ZipUtil.class); public interface ContentProcessor { /** Return null to skip the file */ @Nullable byte[] processContent(byte[] content, File file) throws IOException; } public static void unzipWithProgressSynchronously( @Nullable Project project, @NotNull String progressTitle, @NotNull final File zipArchive, @NotNull final File extractToDir, final boolean unwrapSingleTopLevelFolder) throws GeneratorException { unzipWithProgressSynchronously(project, progressTitle, zipArchive, extractToDir, null, unwrapSingleTopLevelFolder); } public static void unzipWithProgressSynchronously( @Nullable Project project, @NotNull String progressTitle, @NotNull final File zipArchive, @NotNull final File extractToDir, @Nullable final NullableFunction<String, String> pathConvertor, final boolean unwrapSingleTopLevelFolder) throws GeneratorException { final Outcome<Boolean> outcome = DownloadUtil.provideDataWithProgressSynchronously( project, progressTitle, "Unpacking ...", () -> { ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); unzip(progress, extractToDir, zipArchive, pathConvertor, null, unwrapSingleTopLevelFolder); return true; }, () -> false ); Boolean result = outcome.get(); if (result == null) { @SuppressWarnings("ThrowableResultOfMethodCallIgnored") Exception e = outcome.getException(); if (e != null) { throw new GeneratorException("Unpacking failed, downloaded archive is broken"); } throw new GeneratorException("Unpacking was cancelled"); } } private static File getUnzipToDir(@Nullable ProgressIndicator progress, @NotNull File targetDir, boolean unwrapSingleTopLevelFolder) throws IOException { if (progress != null) { progress.setText("Extracting..."); } if (unwrapSingleTopLevelFolder) { return FileUtil.createTempDirectory("unzip-dir-", null); } return targetDir; } // This method will throw IOException, if a zipArchive file isn't a valid zip archive. public static void unzip(@Nullable ProgressIndicator progress, @NotNull File targetDir, @NotNull File zipArchive, @Nullable NullableFunction<String, String> pathConvertor, @Nullable ContentProcessor contentProcessor, boolean unwrapSingleTopLevelFolder) throws IOException { File unzipToDir = getUnzipToDir(progress, targetDir, unwrapSingleTopLevelFolder); ZipFile zipFile = new ZipFile(zipArchive, ZipFile.OPEN_READ); try { Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); InputStream entryContentStream = zipFile.getInputStream(entry); unzipEntryToDir(progress, entry, entryContentStream, unzipToDir, pathConvertor, contentProcessor); entryContentStream.close(); } } finally { zipFile.close(); } doUnwrapSingleTopLevelFolder(unwrapSingleTopLevelFolder, unzipToDir, targetDir); } public static void unzip(@Nullable ProgressIndicator progress, @NotNull File targetDir, @NotNull ZipInputStream stream, @Nullable NullableFunction<String, String> pathConvertor, @Nullable ContentProcessor contentProcessor, boolean unwrapSingleTopLevelFolder) throws IOException { File unzipToDir = getUnzipToDir(progress, targetDir, unwrapSingleTopLevelFolder); ZipEntry entry; while ((entry = stream.getNextEntry()) != null) { unzipEntryToDir(progress, entry, stream, unzipToDir, pathConvertor, contentProcessor); } doUnwrapSingleTopLevelFolder(unwrapSingleTopLevelFolder, unzipToDir, targetDir); } private static void doUnwrapSingleTopLevelFolder(boolean unwrapSingleTopLevelFolder, @NotNull File unzipToDir, @NotNull File targetDir) throws IOException { if (unwrapSingleTopLevelFolder) { File[] topLevelFiles = unzipToDir.listFiles(); File dirToMove; if (topLevelFiles != null && topLevelFiles.length == 1 && topLevelFiles[0].isDirectory()) { dirToMove = topLevelFiles[0]; } else { dirToMove = unzipToDir; } // Don't "FileUtil.moveDirWithContent(dirToMove, targetDir)" // because a file moved with "java.io.File.renameTo" won't inherit its new parent's permissions FileUtil.copyDirContent(dirToMove, targetDir); FileUtil.delete(unzipToDir); } } private static void unzipEntryToDir(@Nullable ProgressIndicator progress, @NotNull final ZipEntry zipEntry, @NotNull final InputStream entryContentStream, @NotNull final File extractToDir, @Nullable NullableFunction<String, String> pathConvertor, @Nullable ContentProcessor contentProcessor) throws IOException { String relativeExtractPath = createRelativeExtractPath(zipEntry); if (pathConvertor != null) { relativeExtractPath = pathConvertor.fun(relativeExtractPath); if (relativeExtractPath == null) { // should be skipped return; } } File child = new File(extractToDir, relativeExtractPath); File dir = zipEntry.isDirectory() ? child : child.getParentFile(); if (!dir.exists() && !dir.mkdirs()) { throw new IOException("Unable to create dir: '" + dir + "'!"); } if (zipEntry.isDirectory()) { return; } if (progress != null) { progress.setText("Extracting " + relativeExtractPath + " ..."); } if (contentProcessor == null) { FileOutputStream fileOutputStream = new FileOutputStream(child); try { FileUtil.copy(entryContentStream, fileOutputStream); } finally { fileOutputStream.close(); } } else { byte[] content = contentProcessor.processContent(FileUtil.loadBytes(entryContentStream), child); if (content != null) { FileUtil.writeToFile(child, content); } } LOG.info("Extract: " + relativeExtractPath); } @NotNull private static String createRelativeExtractPath(@NotNull ZipEntry zipEntry) { String name = StringUtil.trimStart(zipEntry.getName(), "/"); return StringUtil.trimEnd(name, "/"); } }