/******************************************************************************* * Copyright (c) 2014-2016 Red Hat Inc., and others * All rights reserved. This program and the accompanying materials * are 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: * Mickael Istria (Red Hat Inc.) - initial API and implementation * Snjezana Peco (Red Hat Inc.) ******************************************************************************/ package org.eclipse.ui.internal.wizards.datatransfer; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.zip.ZipFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Adapters; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.IImportWizard; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; import org.eclipse.ui.wizards.datatransfer.ProjectConfigurator; /** * This {@link SmartImportWizard} allows user to control an import operation. It * takes as input a directory and assist user in proposing what and how to * import, relying and various strategies contributed as extension of * {@link ProjectConfigurator} * * @since 3.12 * */ public class SmartImportWizard extends Wizard implements IImportWizard { /** * Expands an archive onto provided filesystem directory * @since 3.12 * */ private static final class ExpandArchiveIntoFilesystemOperation implements IRunnableWithProgress { private File archive; private File destination; /** * @param archive * @param destination */ private ExpandArchiveIntoFilesystemOperation(File archive, File destination) { this.archive = archive; this.destination = destination; } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, OperationCanceledException { monitor.beginTask(NLS.bind(DataTransferMessages.SmartImportWizardPage_expandingArchive, archive.getName(), destination.getName()), 1); TarFile tarFile = null; ZipFile zipFile = null; ILeveledImportStructureProvider importStructureProvider = null; try { if (ArchiveFileManipulations.isTarFile(archive.getAbsolutePath())) { tarFile = new TarFile(archive); importStructureProvider = new TarLeveledStructureProvider(tarFile); } else if (ArchiveFileManipulations.isZipFile(archive.getAbsolutePath())) { zipFile = new ZipFile(archive); importStructureProvider = new ZipLeveledStructureProvider(zipFile); } LinkedList<Object> toProcess = new LinkedList<>(); toProcess.add(importStructureProvider.getRoot()); while (!toProcess.isEmpty()) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } Object current = toProcess.pop(); String path = importStructureProvider.getFullPath(current); File toCreate = null; if (path.equals("/")) { //$NON-NLS-1$ toCreate = destination; } else { toCreate = new File(destination, path); } if (importStructureProvider.isFolder(current)) { toCreate.mkdirs(); } else { try (InputStream content = importStructureProvider.getContents(current)) { // known IImportStructureProviders already log an // exception before returning null if (content != null) { Files.copy(content, toCreate.toPath()); } } } List<?> children = importStructureProvider.getChildren(current); if (children != null) { toProcess.addAll(children); } } monitor.worked(1); monitor.done(); } catch (Exception ex) { throw new InvocationTargetException(ex); } finally { if (importStructureProvider != null) { importStructureProvider.closeArchive(); } if (tarFile != null) try { tarFile.close(); } catch (IOException ex) { } if (zipFile != null) try { zipFile.close(); } catch (IOException ex) { } } } } private File initialSelection; private Set<IWorkingSet> initialWorkingSets = new HashSet<>(); private SmartImportRootWizardPage projectRootPage; private SmartImportJob easymportJob; /** * the selected directory or the directory when archive got expanded */ private File directoryToImport; /** * */ public SmartImportWizard() { super(); setNeedsProgressMonitor(true); setForcePreviousAndNextButtons(true); IDialogSettings dialogSettings = getDialogSettings(); if (dialogSettings == null) { dialogSettings = IDEWorkbenchPlugin.getDefault().getDialogSettings(); setDialogSettings(dialogSettings); } setWindowTitle(DataTransferMessages.SmartImportWizardPage_importProjectsInFolderTitle); setDefaultPageImageDescriptor(IDEWorkbenchPlugin.getIDEImageDescriptor("wizban/newprj_wiz.png")); //$NON-NLS-1$ } /** * Sets the initial directory or archive to import in workspace. * * @param directoryOrArchive */ public void setInitialImportSource(File directoryOrArchive) { this.initialSelection = directoryOrArchive; } /** * Sets the initial selected working sets for the wizard * * @param workingSets */ public void setInitialWorkingSets(Set<IWorkingSet> workingSets) { this.initialWorkingSets = workingSets; } @Override public void init(IWorkbench workbench, IStructuredSelection selection) { if (selection != null) { for (Object item : selection.toList()) { File asFile = toFile(item); if (asFile != null && this.initialSelection == null) { this.initialSelection = asFile; } else { IWorkingSet asWorkingSet = Adapters.adapt(item, IWorkingSet.class); if (asWorkingSet != null) { this.initialWorkingSets.add(asWorkingSet); } } } } } /** * Tries to infer a file location from given object, using various * strategies * * @param o * an object * @return a {@link File} associated to this object, or null. */ public static File toFile(Object o) { if (o instanceof File) { return (File)o; } else if (o instanceof IResource) { IPath location = ((IResource)o).getLocation(); return location == null ? null : location.toFile(); } else if (o instanceof IAdaptable) { IResource resource = ((IAdaptable)o).getAdapter(IResource.class); if (resource != null) { IPath location = resource.getLocation(); return location == null ? null : location.toFile(); } } return null; } /** * Tries to infer a file path string from given object, using various * strategies * * @param o * an object * @return a {@link File#getAbsolutePath} associated to this object, or * empty string. */ public static String toAbsolutePath(Object o) { File file = toFile(o); return file == null ? "" : file.getAbsolutePath(); //$NON-NLS-1$ } @Override public void addPages() { this.projectRootPage = new SmartImportRootWizardPage(this, this.initialSelection, this.initialWorkingSets); addPage(this.projectRootPage); } @Override public boolean performFinish() { String[] previousProposals = getDialogSettings().getArray(SmartImportRootWizardPage.IMPORTED_SOURCES); if (previousProposals == null) { previousProposals = new String[0]; } if (!Arrays.asList(previousProposals).contains(this.projectRootPage.getSelectedRoot().getAbsolutePath())) { String[] newProposals = new String[previousProposals.length + 1]; newProposals[0] = this.projectRootPage.getSelectedRoot().getAbsolutePath(); System.arraycopy(previousProposals, 0, newProposals, 1, previousProposals.length); getDialogSettings().put(SmartImportRootWizardPage.IMPORTED_SOURCES, newProposals); } SmartImportJob job = getImportJob(); if (projectRootPage.isDetectNestedProject() || projectRootPage.isConfigureProjects()) { SmartImportJobReportDialog dialog = new SmartImportJobReportDialog(null); dialog.setBlockOnOpen(false); getContainer().getShell().setEnabled(false); dialog.show(job, getShell()); } job.schedule(); return true; } /** * Get the import job that will be processed by this wizard. Can be null (if * provided directory is invalid). * * @return the import job */ public SmartImportJob getImportJob() { final File root = this.projectRootPage.getSelectedRoot(); if (root == null) { return null; } if (root.isDirectory()) { this.directoryToImport = root; } else if (SmartImportWizard.isValidArchive(root)) { this.directoryToImport = getExpandDirectory(root); if (!directoryToImport.isDirectory()) { throw new IllegalArgumentException("Archive wasn't expanded first"); //$NON-NLS-1$ } } else { return null; } if (this.easymportJob == null || !matchesPage(this.easymportJob, this.projectRootPage)) { this.easymportJob = new SmartImportJob(this.directoryToImport, projectRootPage.getSelectedWorkingSets(), projectRootPage.isConfigureProjects(), projectRootPage.isDetectNestedProject()); } if (this.easymportJob != null) { // always update working set on request as the job isn't updated on // WS change automatically this.easymportJob.setWorkingSets(projectRootPage.getSelectedWorkingSets()); } return this.easymportJob; } static boolean isValidArchive(File file) { return ArchiveFileManipulations.isZipFile(file.getAbsolutePath()) || ArchiveFileManipulations.isTarFile(file.getAbsolutePath()); } void expandArchive(final File archive, IProgressMonitor monitor) throws InvocationTargetException, OperationCanceledException { if (!isValidArchive(archive)) { throw new IllegalArgumentException("Input must be an archive"); //$NON-NLS-1$ } this.directoryToImport = getExpandDirectory(archive); ExpandArchiveIntoFilesystemOperation expandOperation = new ExpandArchiveIntoFilesystemOperation(archive, directoryToImport); expandOperation.run(monitor); if (monitor.isCanceled()) { throw new OperationCanceledException(); } } static File getExpandDirectory(final File archive) { if (!isValidArchive(archive)) { throw new IllegalArgumentException("Input must be an archive"); //$NON-NLS-1$ } return new File(ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile(), archive.getName() + "_expanded"); //$NON-NLS-1$ } private static boolean matchesPage(SmartImportJob job, SmartImportRootWizardPage page) { File jobRoot = job.getRoot().getAbsoluteFile(); File pageRoot = page.getSelectedRoot().getAbsoluteFile(); boolean sameSource = jobRoot.equals(pageRoot) || (isValidArchive(pageRoot) && getExpandDirectory(pageRoot).getAbsoluteFile().equals(jobRoot)); return sameSource && job.isDetectNestedProjects() == page.isDetectNestedProject() && job.isConfigureProjects() == page.isConfigureProjects(); } }