/******************************************************************************* * Copyright (c) 2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.windup.ui.internal.archiver; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.operation.ModalContext; import org.eclipse.osgi.util.NLS; import org.jboss.tools.windup.core.WindupCorePlugin; /** * <p> * Operation for exporting a {@link File} and its children to a new .zip or .tar.gz file. * </p> * * <p> * This class is based off of the {@link org.jboss.tools.windup.core.internal.archiver.ui.internal.wizards.datatransfer.ArchiveFileExportOperation} * class used to archive {@link org.eclipse.core.resources.IResource}s, this modified version of the operation is used to export {@link File}s. * </p> * * @see org.jboss.tools.windup.core.internal.archiver.ui.internal.wizards.datatransfer.ArchiveFileExportOperation */ public class ArchiveFileExportOperation implements IRunnableWithProgress { private IFileExporter exporter; private String destinationArchiveName; private IProgressMonitor monitor; private List<File> filesToExport; private File rootResource; private IPath rootArchiveDirectory; private List<IStatus> errorTable = new ArrayList<IStatus>(1); // IStatus private boolean useCompression = true; private boolean useTarFormat = false; /** * <p> * Create an instance of this class. Use this constructor if you wish to export specific resources without a common parent resource. * </p> * * @param files Files to export * @param archiveName Name of archive to export the files too */ public ArchiveFileExportOperation(List<File> files, String archiveName) { super(); // Eliminate redundancies in list of resources being exported Iterator<File> filesIter = files.iterator(); while (filesIter.hasNext()) { File file = filesIter.next(); if (isDescendent(files, file)) { filesIter.remove(); // Removes currentResource; } } this.filesToExport = files; this.destinationArchiveName = archiveName; this.rootArchiveDirectory = null; } /** * <p> * Create an instance of this class. Use this constructor if you wish to recursively export a single resource and all of its descendants. * </p> * * @param file The root file to recursively export to an archive. * @param archiveName Name of archive to export the file and its descendants to */ public ArchiveFileExportOperation(File file, String archiveName) { super(); this.rootResource = file; this.destinationArchiveName = archiveName; this.rootArchiveDirectory = null; } /** * Create an instance of this class. Use this constructor if you wish to export specific resources with a common parent resource (affects * container directory creation) * * @param parentFile Parent file of all of the files to export to the archive * @param filesToExport {@link List} of files to export to the archive using the given parent file as the root file * @param archiveName Name of the archive to export the files to */ public ArchiveFileExportOperation(File parentFile, List<File> filesToExport, String archiveName) { this(parentFile, archiveName); this.filesToExport = filesToExport; } /** * Set this boolean indicating whether exported resources should be compressed (as opposed to simply being stored) * * @param value boolean */ public void setUseCompression(boolean value) { this.useCompression = value; } /** * <p> * Set this boolean indicating whether the file should be output in tar.gz format rather than .zip format. * </p> * * @param value boolean */ public void setUseTarFormat(boolean value) { this.useTarFormat = value; } /** * <p> * Used to set an optional root directory for all archived files to be placed under in the archive. If specified the archive will have one root * directory with this name and all added {@link File}s to the archive will go under this one root directory. * </p> * * @param rootArchiveDirectoryName name of the directory to put at the root of the archive */ public void setRootArchiveDirectoryName(String rootArchiveDirectoryName) { this.rootArchiveDirectory = new Path(rootArchiveDirectoryName); } /** * Export the resources that were previously specified for export (or if a single resource was specified then export it recursively) */ public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException { this.monitor = progressMonitor; try { initialize(); } catch (IOException e) { throw new InvocationTargetException(e, NLS.bind(Messages.ArchiveFileExport_cannotOpen, e.getMessage())); } try { // determine the amount of work to be done int totalWork = IProgressMonitor.UNKNOWN; if (this.filesToExport == null) { totalWork = countChildrenOf(this.rootResource); } else { totalWork = countSelectedFilesToExport(); } this.monitor.beginTask(Messages.ArchiveFileExport_exportingTitle, totalWork); if (this.filesToExport == null) { exportResource(this.rootResource); } else { // ie.- a list of specific resources to export was specified exportSpecifiedResources(); } try { this.exporter.finished(); } catch (IOException e) { throw new InvocationTargetException(e, NLS.bind( Messages.ArchiveFileExport_cannotClose, e.getMessage())); } } finally { this.monitor.done(); } } /** * <p> * Returns the status of the operation. If there were any errors, the result is a status object containing individual status objects for each * error. If there were no errors, the result is a status object with error code <code>OK</code>. * </p> * * @return the status of the operation. */ public IStatus getStatus() { IStatus[] errors = new IStatus[this.errorTable.size()]; this.errorTable.toArray(errors); return new MultiStatus( WindupCorePlugin.PLUGIN_ID, IStatus.OK, errors, Messages.ArchiveFileExport_problemsExporting, null); } /** * <p> * Initialize this operation. * </p> * * @throws java.io.IOException this can happen when performing file IO */ private void initialize() throws IOException { if (this.useTarFormat) { this.exporter = new TarFileExporter(this.destinationArchiveName, this.useCompression); } else { this.exporter = new ZipFileExporter(this.destinationArchiveName, this.useCompression); } } /** * Add a new entry to the error table with the passed information */ private void addError(String message, Throwable e) { this.errorTable.add(new Status(IStatus.ERROR, WindupCorePlugin.PLUGIN_ID, 0, message, e)); } /** * Answer the total number of file resources that exist at or below self in the resources hierarchy. * * @return int * @param checkResource org.eclipse.core.resources.IResource */ private int countChildrenOf(File parentFile) { int count = 0; // count all of the files to archive without using recursion LinkedList<File> filesToCount = new LinkedList<File>(); filesToCount.add(parentFile); while (!filesToCount.isEmpty()) { File fileToCount = filesToCount.pop(); if (fileToCount.isFile()) { ++count; } else { filesToCount.addAll(Arrays.asList(fileToCount.listFiles())); } } return count; } /** * Answer a boolean indicating the number of file resources that were specified for export * * @return number of files to export */ private int countSelectedFilesToExport() { int result = 0; Iterator<File> resources = filesToExport.iterator(); while (resources.hasNext()) { result += countChildrenOf(resources.next()); } return result; } /** * Creates and returns the string that should be used as the name of the entry in the archive. * * @param fileToExport {@link File} to to export * @param leadupDepth the number of resource levels to be included in the path including the resource itself. */ private String createDestinationName(File fileToExport, IPath relativeTo) { IPath fullPath = new Path(fileToExport.getAbsolutePath()); IPath archiveRelativePath = fullPath.makeRelativeTo(relativeTo); // if a root directory is specified then all files should be nested under it if (this.rootArchiveDirectory != null) { archiveRelativePath = this.rootArchiveDirectory.append(archiveRelativePath); } return archiveRelativePath.toString(); } /** * <p> * Export the passed resource to the destination .zip. Export with no path leadup * </p> * * @param fileToExport {@link File} to export. */ private void exportResource(File fileToExport) throws InterruptedException { exportResource(fileToExport, new Path(fileToExport.getParent())); } /** * <p> * Export the passed resource to the destination archive * </p> * * @param fileToExport {@link File} to export * @param leadupDepth the number of directory levels to be included in the path including the {@link File} itself. */ private void exportResource(File fileToExport, IPath relativeTo) throws InterruptedException { if (fileToExport.exists()) { /* * if the File to export is a file export it else if File is a directory recursivly export all of it's childre */ if (fileToExport.isFile()) { String destinationName = createDestinationName(fileToExport, relativeTo); this.monitor.subTask(destinationName); try { this.exporter.write(fileToExport, destinationName); } catch (IOException e) { addError(NLS.bind( Messages.ArchiveFileExport_errorExporting, fileToExport.getPath(), e.getMessage()), e); } monitor.worked(1); ModalContext.checkCanceled(monitor); } else { // create an entry for empty containers File[] children = fileToExport.listFiles(); if (children.length == 0) { String destinationName = createDestinationName(fileToExport, relativeTo); try { this.exporter.write(fileToExport, destinationName + IPath.SEPARATOR); } catch (IOException e) { addError(NLS.bind( Messages.ArchiveFileExport_errorExporting, fileToExport.getPath(), e.getMessage()), e); } } for (int i = 0; i < children.length; i++) { exportResource(children[i], relativeTo); } } } } /** * <p> * Export the files contained in the previously-defined filesToExport collection * </p> */ private void exportSpecifiedResources() throws InterruptedException { Iterator<File> resources = filesToExport.iterator(); while (resources.hasNext()) { exportResource(resources.next()); } } /** * <p> * Answer a boolean indicating whether the passed child is a descendant of one or more members of the passed resources collection * </p> * * @param parentFiles Check if the given child is a child of any of these parent files * @param child Check if this child is a child of any of the given parent files * * @return <code>true</code> if the given file is a descendant of any of the given parent files, <code>false</code> otherwise */ private boolean isDescendent(List<File> parentFiles, File child) { if (child != null) { File parent = child.getParentFile(); if (parentFiles.contains(parent)) { return true; } return isDescendent(parentFiles, parent); } else { return false; } } }