/******************************************************************************* * Copyright (c) 2013, 2014 Red Hat, Inc. * 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: * Red Hat Inc. - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.thym.ui.internal.wizard.imports; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IColorProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.thym.core.HybridProject; import org.eclipse.thym.core.config.Widget; import org.eclipse.thym.core.config.WidgetModel; import org.eclipse.thym.core.engine.HybridMobileEngineManager; import org.eclipse.thym.core.platform.PlatformConstants; import org.eclipse.thym.core.plugin.CordovaPlugin; import org.eclipse.thym.core.plugin.CordovaPluginManager; import org.eclipse.thym.core.plugin.FileOverwriteCallback; import org.eclipse.thym.ui.HybridUI; import org.eclipse.thym.ui.internal.status.StatusManager; import org.eclipse.thym.ui.wizard.project.HybridProjectCreator; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.IOverwriteQuery; import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider; import org.eclipse.ui.wizards.datatransfer.ImportOperation; public class HybridProjectImportPage extends WizardPage implements IOverwriteQuery{ private class ProjectCandidate { private Widget widget; File wwwLocation; File configLocation; boolean conflicts; public ProjectCandidate(File www, File config){ this.configLocation = config; this.wwwLocation = www; } String getLabel(){ if(getWidget() == null ){ return NLS.bind("INVALID {0}",wwwLocation.toString()); } String appName = getProjectName(); return NLS.bind("{0} ({1})", new String[]{appName,wwwLocation.toString() }); } String getProjectName() { String projectName = getWidget().getId(); if(widget.getName() != null ){ projectName = getWidget().getName(); } return projectName; } boolean exists(){ if(getWidget() == null ) return true; IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); final String projectName = getProjectName(); IPath wsPath = root.getLocation(); IPath localProjectPath = wsPath.append(projectName); IProject project = root.getProject(projectName); return project.exists() || localProjectPath.toFile().exists(); } Widget getWidget(){ if(widget == null ){ try { widget = WidgetModel.parseToWidget(configLocation); } catch (CoreException e) { HybridUI.log(IStatus.ERROR, "Error parsing the config.xml for import project", e); } } return widget; } } private final class ProjectCandidateLabelProvider extends LabelProvider implements IColorProvider{ public String getText(Object element) { return ((ProjectCandidate) element).getLabel(); } @Override public Color getForeground(Object element) { ProjectCandidate candie = (ProjectCandidate) element; if(candie.conflicts || candie.exists()){ return getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); } return null; } @Override public Color getBackground(Object element) { return null; } } private final static String SETTINGSKEY_DIRECTORIES = "HybridProjectImportPage.DIRECTORIES";//$NON-NLS-1$ private final static String SETTINGSKEY_COPY = "HybridProjectImportPage.COPY";//$NON-NLS-1$ private Combo directoryPathField; private String previouslyBrowsedDirectory =""; private ProjectCandidate[] candidates; private CheckboxTreeViewer projectList; private Button copyCheckbox; private boolean copyFiles; protected HybridProjectImportPage() { super("HybridProjectImportPage"); setTitle("Cordova Project Import"); setDescription("Select a directory to search for Cordova projects"); } @Override public void createControl(final Composite parent) { initializeDialogUnits(parent); Composite workArea = new Composite(parent, SWT.NONE); setControl(workArea); GridLayoutFactory.fillDefaults().applyTo(workArea); GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(workArea); createProjectRoot(workArea); createProjectsList(workArea); createOptionsGroup(workArea); restoreFromHistory(); setPageComplete(validatePage()); Dialog.applyDialogFont(workArea); } private void createProjectsList( final Composite workArea) { final Label projectsLabel = new Label(workArea,SWT.NULL); projectsLabel.setText("Projects:"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(projectsLabel); Composite projectListGroup = new Composite(workArea, SWT.NULL); GridDataFactory.fillDefaults().grab(true, true).applyTo(projectListGroup); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(projectListGroup); projectList = new CheckboxTreeViewer(projectListGroup); PixelConverter pc = new PixelConverter(projectList.getControl()); GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL) .hint(pc.convertWidthInCharsToPixels(25),pc.convertHeightInCharsToPixels(10)).applyTo(projectList.getControl()); projectList.setLabelProvider(new ProjectCandidateLabelProvider()); projectList.setContentProvider(new ITreeContentProvider() { @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public void dispose() { } @Override public boolean hasChildren(Object element) { return false; } @Override public Object getParent(Object element) { return null; } @Override public Object[] getElements(Object inputElement) { if(candidates == null ) return new ProjectCandidate[0]; return candidates; } @Override public Object[] getChildren(Object parentElement) { return null; } }); projectList.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { ProjectCandidate candidate = (ProjectCandidate)event.getElement(); //Cancel out existing projects if(candidate.exists()){ projectList.setChecked(candidate, false); return; } updateConflicts(candidate, event.getChecked()); projectList.refresh(true); setPageComplete(validatePage()); } }); final Composite selectButtonGroup = new Composite(projectListGroup, SWT.NULL); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(selectButtonGroup); GridLayoutFactory.fillDefaults().applyTo(selectButtonGroup); Button selectAll = new Button(selectButtonGroup,SWT.PUSH); selectAll.setText("Select All"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(selectAll); Button deselectAll = new Button(selectButtonGroup, SWT.PUSH); deselectAll.setText("Deselect All"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(deselectAll); Button refresh = new Button(selectButtonGroup, SWT.PUSH); refresh.setText("Refresh"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(refresh); selectAll.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { if(candidates != null ){ for (ProjectCandidate candie : candidates) { if(!candie.conflicts && !candie.exists()){ projectList.setChecked(candie, true); updateConflicts(candie, true); } } } setPageComplete(validatePage()); } }); deselectAll.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { if (candidates != null) { for (ProjectCandidate candie : candidates) { projectList.setChecked(candie, false); updateConflicts(candie, false); } } setPageComplete(false); } }); refresh.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { updateProjectsList(directoryPathField.getText()); } }); projectList.setInput(this); } private void createProjectRoot(final Composite workArea) { final Composite projectRootGroup = new Composite(workArea, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(3).margins(0, 10).applyTo(projectRootGroup); GridDataFactory.fillDefaults().grab(true,false).align(SWT.FILL,SWT.FILL).applyTo(projectRootGroup); final Label directoryLabel = new Label(projectRootGroup, SWT.NULL); directoryLabel.setText("Select root directory:"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(directoryLabel); directoryPathField = new Combo(projectRootGroup, SWT.BORDER); PixelConverter pixelConverter = new PixelConverter(directoryPathField); GridDataFactory.fillDefaults().grab(true, false).hint(pixelConverter.convertWidthInCharsToPixels(25),SWT.DEFAULT).applyTo(directoryPathField); final Button browseDirectoriesButton = new Button(projectRootGroup, SWT.PUSH); browseDirectoriesButton.setText("Browse..."); GridDataFactory.fillDefaults().applyTo(browseDirectoriesButton); browseDirectoriesButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { handleBrowseButtonPressed(); } }); directoryPathField.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { updateProjectsList(directoryPathField.getText()); } }); } private void handleBrowseButtonPressed() { final DirectoryDialog dialog = new DirectoryDialog( directoryPathField.getShell(), SWT.SHEET); dialog.setMessage("Select search directory"); String dirName = directoryPathField.getText().trim(); if (dirName.isEmpty()) { dirName = previouslyBrowsedDirectory; } if (dirName.isEmpty()) { dialog.setFilterPath(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString()); } else { File path = new File(dirName); if (path.exists()) { dialog.setFilterPath(new Path(dirName).toOSString()); } } String selectedDirectory = dialog.open(); if (selectedDirectory != null) { previouslyBrowsedDirectory = selectedDirectory; directoryPathField.setText(previouslyBrowsedDirectory); updateProjectsList(selectedDirectory); } } private void updateConflicts(ProjectCandidate candidate, boolean checked){ for(ProjectCandidate elem: candidates ){ Widget w1 = elem.getWidget(); Widget w2 = candidate.getWidget(); if(w1.getId().equals(w2.getId()) && w1.getName().equals(w2.getName()) && !elem.configLocation.equals(candidate.configLocation)){ if(projectList.getChecked(elem)){ projectList.setChecked(candidate, false); }else{ elem.conflicts = checked; } } } } private void updateProjectsList(final String selectedDirectory) { if(selectedDirectory == null || selectedDirectory.isEmpty()){ candidates = null; projectList.refresh(true); return; } final File directory = new File(selectedDirectory); try { getContainer().run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { if(directory.isDirectory()){ List<ProjectCandidate> candies= new ArrayList<HybridProjectImportPage.ProjectCandidate>(); collectProjectCandidates(candies, directory, monitor); candidates = candies.toArray(new ProjectCandidate[candies.size()]); } } }); } catch (InvocationTargetException e) { if (e.getTargetException() != null) { if(e.getTargetException() instanceof CoreException ){ StatusManager.handle((CoreException) e.getTargetException()); }else{ ErrorDialog.openError(getShell(), "Error finding projects to import",null, new Status(IStatus.ERROR, HybridUI.PLUGIN_ID, "Error while searching for projects to import", e.getTargetException() )); } } } catch (InterruptedException e) { HybridUI.log(IStatus.ERROR, "Error searchig projects to import", e); } projectList.refresh(true); } protected void collectProjectCandidates(List<ProjectCandidate> candidates, File directory, IProgressMonitor monitor) { if(monitor.isCanceled()){ return; } File[] configXMLs = directory.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return PlatformConstants.FILE_XML_CONFIG.equals(name); } }); if(configXMLs == null){ return; } for (File config: configXMLs) { File parent = config.getParentFile(); ProjectCandidate candidate = null; if (config.isFile()) { if (parent.getName().equals(PlatformConstants.DIR_WWW)) { candidate = new ProjectCandidate(parent, config); } else { File sameLevelWWW = new File(parent, PlatformConstants.DIR_WWW); if (sameLevelWWW.isDirectory()) { candidate = new ProjectCandidate(sameLevelWWW, config); } } } if(candidate != null){ candidates.add(candidate); return; } } File[] dirs = directory.listFiles(); for (File dir : dirs) { collectProjectCandidates(candidates, dir, monitor); } } private void createOptionsGroup(Composite workArea) { final Group optionsGroup = new Group(workArea, SWT.NULL); optionsGroup.setText("Options:"); GridLayoutFactory.fillDefaults().margins(10,10).applyTo(optionsGroup); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.FILL).applyTo(optionsGroup); copyCheckbox = new Button(optionsGroup, SWT.CHECK); copyCheckbox.setText("Copy into workspace"); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL); copyCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { copyFiles = copyCheckbox.getSelection(); setPageComplete(validatePage()); } }); } private void restoreFromHistory(){ //Directories IDialogSettings settings = getDialogSettings(); if (settings == null) return; String[] sourceNames = settings.getArray(SETTINGSKEY_DIRECTORIES); if (sourceNames == null) { return; } for (String dirname : sourceNames) { directoryPathField.add(dirname); } //copy to workspace copyFiles = settings.getBoolean(SETTINGSKEY_COPY); copyCheckbox.setSelection(copyFiles); } private void saveInHistroy(){ IDialogSettings settings = getDialogSettings(); if (settings != null) { // Directories String[] sourceNames = settings.getArray(SETTINGSKEY_DIRECTORIES); if (sourceNames == null) { sourceNames = new String[0]; } List<String> l = new ArrayList<String>(Arrays.asList(sourceNames)); l.remove(directoryPathField.getText()); l.add(0,directoryPathField.getText()); sourceNames = l.toArray(new String[l.size()]); settings.put(SETTINGSKEY_DIRECTORIES, sourceNames); //Copy to workspace settings.put(SETTINGSKEY_COPY, copyCheckbox.getSelection()); } } boolean createProjects(){ saveInHistroy(); final FileOverwriteCallback cb = new FileOverwriteCallback() { @Override public boolean isOverwiteAllowed(String[] files) { return true; } }; final Object[] selectedCandidates = projectList.getCheckedElements(); WorkspaceModifyOperation wop = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("", selectedCandidates.length); if (monitor.isCanceled()) { throw new OperationCanceledException(); } try { for (int i = 0; i < selectedCandidates.length; i++) { ProjectCandidate pc = (ProjectCandidate) selectedCandidates[i]; IProject prj = doCreateProject(pc, new SubProgressMonitor(monitor, 1)); HybridProject project = HybridProject.getHybridProject(prj); } } catch(CoreException e){ throw new InvocationTargetException(e); }finally{ monitor.done(); } } }; try { getContainer().run(true, true, wop); } catch (InvocationTargetException e) { if (e.getTargetException() != null) { if(e.getTargetException() instanceof CoreException ){ StatusManager.handle((CoreException) e.getTargetException()); }else{ ErrorDialog.openError(getShell(), "Error importing project",null, new Status(IStatus.ERROR, HybridUI.PLUGIN_ID, "Project import error", e.getTargetException() )); } } } catch (InterruptedException e) { throw new OperationCanceledException(); } return true; } private IProject doCreateProject(ProjectCandidate pc, IProgressMonitor monitor) throws CoreException, InterruptedException { HybridProjectCreator projectCreator = new HybridProjectCreator(); Widget w = pc.getWidget(); String projectName = pc.getProjectName(); URI location = null; if(!copyFiles){ location = pc.wwwLocation.getParentFile().toURI(); } IProject project = projectCreator.createProject(projectName, location, w.getName(), w.getId(), null, monitor); if(copyFiles){ ImportOperation operation = new ImportOperation(project .getFullPath(), pc.wwwLocation.getParentFile(), FileSystemStructureProvider.INSTANCE , this); operation.setContext(getShell()); operation.setOverwriteResources(true); operation.setCreateContainerStructure(false); try { operation.run(monitor); } catch (InvocationTargetException e) { if(e.getCause() != null && e.getCause() instanceof CoreException){ CoreException corex = (CoreException) e.getCause(); throw corex; } } IStatus status = operation.getStatus(); if (!status.isOK()) throw new CoreException(status); } return project; } private boolean validatePage(){ final Object[] selectedCandidates = projectList.getCheckedElements(); if(selectedCandidates.length < 1 ){ String msg = null; if(candidates != null ){ for (ProjectCandidate candie : candidates){ if(candie.exists()){ msg = "Some projects cannot be imported because they already exist in the workspace"; } } } setMessage(msg, WARNING); setErrorMessage(null); return false; } IWorkspace workspace = ResourcesPlugin.getWorkspace(); for (int i = 0; i < selectedCandidates.length; i++) { ProjectCandidate pc = (ProjectCandidate) selectedCandidates[i]; final IStatus nameStatus= workspace.validateName(pc.getProjectName(), IResource.PROJECT); if (!nameStatus.isOK()) { setErrorMessage(nameStatus.getMessage()); return false; } final IProject handle= workspace.getRoot().getProject(pc.getProjectName()); if (handle.exists()) { setErrorMessage(NLS.bind("Project {0} already exists in the workspace", pc.getProjectName())); return false; } IPath projectLocation= ResourcesPlugin.getWorkspace().getRoot().getLocation().append(pc.getProjectName()); if (projectLocation.toFile().exists()) { try { //correct casing String canonicalPath= projectLocation.toFile().getCanonicalPath(); projectLocation= new Path(canonicalPath); } catch (IOException e) { HybridUI.log(IStatus.WARNING, "Error getting canonical path", e); } String existingName= projectLocation.lastSegment(); if (!existingName.equals(pc.getProjectName())) { setErrorMessage(NLS.bind("Project name {0} already exists on the workspace", existingName)); return false; } } IPath projectPath = null; if(!copyFiles){ projectPath = new Path(pc.wwwLocation.getParentFile().toString()); } final IStatus locationStatus= workspace.validateProjectLocation(handle, projectPath); if (!locationStatus.isOK()) { setErrorMessage(locationStatus.getMessage()); return false; } //Warn for an existing eclipse project File[] files = pc.wwwLocation.getParentFile().listFiles(); for (File file : files) { if(file.isFile() && IProjectDescription.DESCRIPTION_FILE_NAME.equals(file.getName())){ setMessage(NLS.bind("A project description for {0} already exists and will be replaced, use existing project import to restore the project", pc.getProjectName()), IStatus.WARNING); return false; } } } setMessage(null); setErrorMessage(null); return true; } @Override public String queryOverwrite(String pathString) { return IOverwriteQuery.ALL; } }