/******************************************************************************* * Copyright (c) 2014 Mentor Graphics 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: * Mentor Graphics - initial API and implementation *******************************************************************************/ package com.codesourcery.internal.installer.ui; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; 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.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.jface.wizard.Wizard; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import com.codesourcery.installer.IInstallData; import com.codesourcery.installer.IInstallDescription; import com.codesourcery.installer.IInstallManager; import com.codesourcery.installer.IInstallMode; import com.codesourcery.installer.IInstallProduct; import com.codesourcery.installer.IInstallWizardPage; import com.codesourcery.installer.Installer; import com.codesourcery.installer.LaunchItem; import com.codesourcery.installer.ui.InstallWizardPage; import com.codesourcery.internal.installer.InstallMessages; import com.codesourcery.internal.installer.ui.pages.ProductsPage; import com.codesourcery.internal.installer.ui.pages.ProgressPage; import com.codesourcery.internal.installer.ui.pages.ResultsPage; import com.codesourcery.internal.installer.ui.pages.SummaryPage; /** * Install wizard * This wizard shows pages that implement <code>IInstallWizardPage</code>. */ public class InstallWizard extends Wizard { /** * Summary page */ private SummaryPage summaryPage; /** * Progress page */ private ProgressPage progressPage; /** * Result page */ private ResultsPage resultsPage; /** * <code>true</code> if installation complete */ private boolean installed = false; /** * Products page */ private ProductsPage productsPage; /** * Status of install operation. */ private IStatus status; /** * Title image */ private Image titleImage; /** * Products that were uninstalled or <code>null</code>. */ private IInstallProduct[] uninstalledProducts; /** * Constructor */ public InstallWizard() { setNeedsProgressMonitor(true); // Set window title String title; if (isInstall()) { title = getInstallManager().getInstallDescription().getWindowTitle(); if (title == null) { title = MessageFormat.format(InstallMessages.Title0, getInstallManager().getInstallDescription().getProductName()); } } else { title = InstallMessages.UninstallTitle; } setWindowTitle(title); // Load title image if available try { if (isInstall()) { IPath titleImagePath = getInstallManager().getInstallDescription().getTitleImage(); if (titleImagePath != null) { if (titleImagePath.toFile().exists()) { ImageLoader imageLoader = new ImageLoader(); ImageData[] imageDatas = imageLoader.load(titleImagePath.toOSString()); if (imageDatas.length > 0) { titleImage = new Image(Display.getDefault(), imageDatas[0]); } } else { Installer.log("Missing title image file: " + titleImagePath.toOSString()); } } } } catch (Exception e) { Installer.log(e); } } /** * Returns the install manager. * * @return Install manager */ protected IInstallManager getInstallManager() { return Installer.getDefault().getInstallManager(); } /** * Returns the title image specified in the installer properties. * * @return Title image or <code>null</code> */ public Image getTitleImage() { return titleImage; } /** * @return The text used for finish. */ public String getFinishText() { IInstallMode mode = getInstallManager().getInstallMode(); if (mode.isInstall()) { IInstallDescription desc = getInstallManager().getInstallDescription(); return mode.isMirror() ? desc.getText(IInstallDescription.TEXT_FINISH_MIRROR, InstallMessages.Save) : desc.getText(IInstallDescription.TEXT_FINISH_INSTALL, InstallMessages.Install); } else { return InstallMessages.Uninstall; } } /** * Returns the install data. * * @return Install data */ public IInstallData getInstallData() { return Installer.getDefault().getInstallManager().getInstallData(); } /** * Returns if the wizard is for * an installation. * * @return <code>true</code> if install * <code>false</code> if uninstall */ public boolean isInstall() { return getInstallManager().getInstallMode().isInstall(); } /** * Returns if the wizard is for a mirror. * * @return <code>true</code> if mirror */ public boolean isMirror() { return getInstallManager().getInstallMode().isMirror(); } /** * Returns if the wizard is for an upgrade. * @return <code>true</code> if upgrade */ public boolean isUpgrade() { return getInstallManager().getInstallMode().isUpgrade(); } /** * Returns the status of the install operation. * * @return Status */ public IStatus getStatus() { return status; } @Override public boolean performCancel() { boolean confirm = true; // If not showing Results page, confirm the cancel if (getContainer().getCurrentPage() != resultsPage) { confirm = showCancelConfirmation(); } // Show cancel confirmation if (confirm) { status = new Status(IStatus.CANCEL, Installer.ID, 0, "Cancelled", null); // Cleanup any install files try { if (isInstall()) Installer.getDefault().getInstallManager().setInstallLocation(null, null); } catch (CoreException e) { Installer.log(e); } return super.performCancel(); } else { return false; } } /** * Shows a cancel confirmation dialog. * * @return <code>true</code> to cancel */ public boolean showCancelConfirmation() { // Confirmation message String confirmationMessage; if (isInstall()) { confirmationMessage = MessageFormat.format(InstallMessages.CancelInstallConfirmation2, new Object[] { Installer.getDefault().getInstallManager().getInstallDescription().getProductName(), InstallMessages.Resume, InstallMessages.Quit }); } else { confirmationMessage = MessageFormat.format(InstallMessages.CancelUninstallConfirmation1, new Object[] { InstallMessages.Resume, InstallMessages.Quit }); } // Confirm dialog MessageDialog confirmationDialog = new MessageDialog( getContainer().getShell(), InstallMessages.CancelSetupTitle, null, confirmationMessage, MessageDialog.QUESTION, new String[] { InstallMessages.Resume, InstallMessages.Quit }, 0 ); return (confirmationDialog.open() != 0); } @Override public boolean performFinish() { boolean complete = true; // First time - perform installation/uninstallation if (!installed) { // Install if (isInstall()) { // Save page install data for (IInstallWizardPage page : getSupportedPages()) { try { page.saveInstallData(getInstallData()); } catch (CoreException e) { InstallWizardPage wizardPage = (InstallWizardPage)page; // If page has error, show it and return wizardPage.showStatus(new IStatus[] { e.getStatus() }); wizardPage.setPageComplete(false); getContainer().showPage(page); return false; } } // Show the summary page and validate the installation if (summaryPage != null) { getContainer().showPage(summaryPage); // Installation not valid, don't proceed if (!summaryPage.validate()) { return false; } } // Show installing page getContainer().showPage(progressPage); status = install(); } // Uninstall else { // Show uninstalling page getContainer().showPage(progressPage); status = uninstall(); } complete = false; installed = true; // Set result if (resultsPage != null) { // Show results page launch options boolean showOptions = false; // Success if (status.isOK()) { String message; // Install message if (isInstall()) { IInstallDescription desc = Installer.getDefault().getInstallManager().getInstallDescription(); StringBuilder buffer = new StringBuilder(); // Mirror operation if (getInstallManager().getInstallMode().isMirror()) { String resultMessage = desc.getText(IInstallDescription.TEXT_RESULT_MIRROR, NLS.bind(InstallMessages.MirrorComplete0, getInstallManager().getInstallDescription().getProductName())); buffer.append(resultMessage); } // Install else { String resultMessage = desc.getText(IInstallDescription.TEXT_RESULT_INSTALL, NLS.bind(InstallMessages.InstallationComplete0, getInstallManager().getInstallDescription().getProductName())); buffer.append(resultMessage); String installText = getInstallManager().getInstallDescription().getText(IInstallDescription.TEXT_INSTALL_ADDENDUM, null); if (installText != null) { buffer.append("\n\n"); buffer.append(installText); } } buffer.append("\n\n"); buffer.append(InstallMessages.ClickClose); message = buffer.toString(); // Show options if not mirror operation showOptions = !getInstallManager().getInstallMode().isMirror(); } // Uninstall message else { StringBuilder buffer = new StringBuilder(InstallMessages.UninstallationComplete); IInstallProduct[] uninstalledProducts = getUninstalledProducts(); if (uninstalledProducts != null) { buffer.append("\n\n"); // Single product that specifies uninstall text if (uninstalledProducts.length == 1) { String uninstallText = uninstalledProducts[0].getProperty(IInstallProduct.PROPERTY_UNINSTALL_TEXT); if (uninstallText != null) { buffer.append(uninstallText); } } // Multiple products that specify uninstall text else { for (IInstallProduct uninstalledProduct : uninstalledProducts) { String uninstallText = uninstalledProduct.getProperty(IInstallProduct.PROPERTY_UNINSTALL_TEXT); if (uninstallText != null) { buffer.append("<b>" + uninstalledProduct.getName() + "</b>\n"); buffer.append(uninstallText); buffer.append("\n\n"); } } } } message = buffer.toString(); showOptions = false; } resultsPage.setResult(message, showOptions); } // Cancel else if (status.getSeverity() == IStatus.CANCEL) { String message; if (isInstall()) { message = Installer.getDefault().getInstallManager().getInstallMode().isUpdate() ? NLS.bind(InstallMessages.UpdateCancelled0, getInstallManager().getInstallDescription().getProductName()) : NLS.bind(InstallMessages.InstallationCancelled0, getInstallManager().getInstallDescription().getProductName()); } else { message = InstallMessages.UninstallationCancelled; } resultsPage.setResult(message, false); } // Error else { resultsPage.setError(getStatusMessage(status)); } // Show install complete page getContainer().showPage(resultsPage); } } // Second time - installation has // been completed else { // Launch selected product items if no errors at end of installation if (isInstall() && (resultsPage != null) && !resultsPage.hasError()) { LaunchItem[] launchItems = getInstallManager().getInstallDescription().getLaunchItems(); if (launchItems != null) { for (LaunchItem launchItem : launchItems) { try { if (resultsPage.isItemChecked(launchItem)) { getInstallManager().launch(launchItem); } } catch (Exception e) { Installer.log(e); } } } } } return complete; } /** * Returns a status message. * * @param status Status * @return Message */ private String getStatusMessage(IStatus status) { StringBuilder message = new StringBuilder(); if (status != null) { message.append(status.getMessage()); IStatus[] children = status.getChildren(); if (children != null) { for (IStatus child : children) { message.append('\n'); message.append(child.getMessage()); } } Throwable exception = status.getException(); if (exception instanceof InvocationTargetException) { exception = ((InvocationTargetException)exception).getTargetException(); } if (exception != null) { message.append('\n'); message.append(exception.getLocalizedMessage()); } } return message.toString(); } /** * Performs the uninstallation. * * @return Status * @throws CoreException on failure */ private IStatus uninstall() { Throwable error = null; final IStatus[] status = new IStatus[] { Status.OK_STATUS }; final IInstallProduct[] products = productsPage.getSelectedProducts(); try { getContainer().run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { // Remove products getInstallManager().uninstall(products, monitor); if (monitor.isCanceled()) status[0] = Status.CANCEL_STATUS; uninstalledProducts = products; } catch (Exception e) { throw new InvocationTargetException(e); } } }); } catch (Exception e) { error = e; } // Install operation failed if (error != null) { status[0] = new Status(IStatus.ERROR, Installer.ID, 0, InstallMessages.UninstallationFailed, error); } return status[0]; } /** * Performs the installation * * @return Status * @throws CoreException on failure */ private IStatus install() { final IStatus[] status = new IStatus[] { Status.OK_STATUS }; try { // Install getContainer().run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { getInstallManager().install(monitor); if (monitor.isCanceled()) { status[0] = Status.CANCEL_STATUS; } } catch (CoreException e) { Installer.log(e); throw new InvocationTargetException(e); } } }); } catch (Exception e) { status[0] = new Status(IStatus.ERROR, Installer.ID, 0, "", e); } return status[0]; } /** * Returns the progress page. * * @return Progress page */ public ProgressPage getProgressPage() { return progressPage; } /** * Sets up page navigation. */ public void setupPageNavigation() { IWizardPage[] pages = getPages(); // Initialize page labels for (IWizardPage page : pages) { if (page instanceof InstallWizardPage) { InstallWizardPage wizardPage = (InstallWizardPage)page; wizardPage.initPageLabel(); } } // Setup page navigation for (IWizardPage page : pages) { if (page instanceof InstallWizardPage) { InstallWizardPage wizardPage = (InstallWizardPage)page; wizardPage.initPageLabel(); wizardPage.setupNavigation(); } } } @Override public void addPages() { // Add install wizard pages from modules IInstallWizardPage[] installPages = getInstallManager().getWizardPages(); for (IInstallWizardPage installPage : installPages) { addPage(installPage); } // Install summary page summaryPage = new SummaryPage("summaryPage", InstallMessages.SummaryPageTitle); addPage(summaryPage); // Uninstall products page productsPage = new ProductsPage("productsPage", InstallMessages.ProductsPageTitle, InstallMessages.ProductsMessage); addPage(productsPage); // Progress page progressPage = new ProgressPage("progressPage"); addPage(progressPage); // Results page resultsPage = new ResultsPage("resultsPage", InstallMessages.ResultsPageTitle); addPage(resultsPage); } @Override public void createPageControls(Composite pageContainer) { super.createPageControls(pageContainer); // Initialize the wizard pages navigation unless the Setup page is // present as it will do it. if (!setupPageAvailable()) { setupPageNavigation(); } } /** * @return Returns if a setup page is available. */ private boolean setupPageAvailable() { boolean available = false; IWizardPage[] pages = getPages(); for (IWizardPage page : pages) { if (page instanceof ISetupWizardPage) { IInstallWizardPage installPage = (IInstallWizardPage)page; if (installPage.isSupported()) { available = true; break; } } } return available; } /** * Returns is a page is currently supported. * * @param page Page * @return <code>true</code> if page is supported */ private boolean isPageSupported(IWizardPage page) { boolean supported = true; if (page instanceof IInstallWizardPage) { supported = ((IInstallWizardPage)page).isSupported(); } return supported; } /** * Returns the products that were uninstalled. * * @return Products that were uninstalled or <code>null</code> if the uninstall operation has not been performed * yet or the operation performed was an installation. */ private IInstallProduct[] getUninstalledProducts() { return uninstalledProducts; } @Override public IWizardPage getStartingPage() { IWizardPage startPage = null; IWizardPage[] pages = getPages(); for (IWizardPage page : pages) { if (isPageSupported(page)) { startPage = page; break; } } if (startPage instanceof IInstallWizardPage) { ((IInstallWizardPage)startPage).setActive(getInstallData()); } return startPage; } @Override public IWizardPage getPreviousPage(IWizardPage page) { IWizardPage previousPage = super.getPreviousPage(page); // Skip pages that are not supported in current mode while ((previousPage != null) && !isPageSupported(previousPage)) { previousPage = super.getPreviousPage(previousPage); } return previousPage; } @Override public IWizardPage getNextPage(IWizardPage page) { IWizardPage nextPage = super.getNextPage(page); // Skip pages that are not supported in current mode while ((nextPage != null) && !isPageSupported(nextPage)) { nextPage = super.getNextPage(nextPage); } return nextPage; } /** * Runs a long operation and shows a wizard page as busy. * * @param wizardPage Wizard page * @param message Busy message * @param runnable Runnable to execute */ public void runOperation(InstallWizardPage wizardPage, String message, Runnable runnable) { internalRunOperation(wizardPage, message, runnable, 0); } /** * Runs a long operation and shows a wizard page as busy if the operation * takes longer than the specified time. * * @param wizardPage Wizard page * @param message Busy message * @param runnable Runnable to execute * @param delay Delay (ms) before showing busy */ public void runOperation(InstallWizardPage wizardPage, String message, Runnable runnable, int delay) { internalRunOperation(wizardPage, message, runnable, delay); } /** * Runs a long operation and shows a wizard page as busy if the operation * takes more than the specified time. * * @param wizardPage Wizard page * @param message Busy message * @param runnable Runnable to execute * @param delay Delay before busy is shown */ private void internalRunOperation(InstallWizardPage wizardPage, String message, Runnable runnable, int delay) { // Run job OperationJob job = new OperationJob(wizardPage, message, runnable, delay); job.schedule(); // Dispatch UI events until job is complete Display display = getShell().getDisplay(); while ((getShell() != null) && !getShell().isDisposed()) { if (!display.readAndDispatch()) display.sleep(); if (job.isDone()) break; } } /** * Job to perform long running operation. */ private class OperationJob extends Job { /** Wizard page */ private InstallWizardPage installPage; /** Operation to perform */ private Runnable runnable; /** Busy message */ private String message; /** <code>true</code> if operation is complete */ private boolean done = false; /** Delay before showing busy animation */ private int delay; /** * Constructor * * @param installPage Install wizard page * @param message Busy message * @param runnable Operation to perform * @param delay Delay before showing busy animation (ms) or <code>0</code> * to display immediately. */ public OperationJob(InstallWizardPage installPage, String message, Runnable runnable, int delay) { super("Page Operation Job"); this.installPage = installPage; this.message = message; this.runnable = runnable; this.delay = delay; setSystem(true); } /** * Returns the delay before the busy animation will be displayed. * * @return Delay before busy animation is displayed (ms) */ public int getDelay() { return delay; } /** * Returns if the operation is complete. * * @return <code>true</code> if operation is complete */ public boolean isDone() { return done; } /** * Sets the operation complete. */ private void setDone() { done = true; } @Override protected IStatus run(IProgressMonitor monitor) { try { // Disable buttons getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { ((InstallWizardDialog)getContainer()).setButtonsEnabled(false); } }); // Show busy animation if operation takes too long Timer busyTimer = new Timer(); if (getDelay() == 0) { // Show page busy getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { if (!installPage.isBusy()) { installPage.showBusy(message); } } }); } else { busyTimer.schedule(new TimerTask() { @Override public void run() { // Show page busy getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { if (!installPage.isBusy()) { installPage.showBusy(message); } } }); } }, getDelay()); } getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { getShell().update(); } }); // Run operation try { runnable.run(); } catch (Exception e) { Installer.log(e); } busyTimer.cancel(); // Hide page busy getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { installPage.hideBusy(); ((InstallWizardDialog)getContainer()).setButtonsEnabled(true); // Update wizard to allow outstanding paints to occur. This is needed when the busy operation // is during a page transition to avoid unwanted artifacts. getShell().update(); } }); } catch (Exception e) { Installer.log(e); } finally { setDone(); } return Status.OK_STATUS; } } /** * Returns all wizard pages that are currently supported. * * @return Supported wizard pages */ public IInstallWizardPage[] getSupportedPages() { ArrayList<IInstallWizardPage> supportedPages = new ArrayList<IInstallWizardPage>(); IWizardPage[] pages = getPages(); for (IWizardPage page : pages) { if (page instanceof IInstallWizardPage) { IInstallWizardPage wizardPage = (IInstallWizardPage)page; if (wizardPage.isSupported()) { supportedPages.add(wizardPage); } } } return supportedPages.toArray(new IInstallWizardPage[supportedPages.size()]); } @Override public boolean canFinish() { // Check if all pages report can finish boolean finish = true; for (IInstallWizardPage page : getSupportedPages()) { if (!page.isPageComplete()) { finish = false; break; } } return finish; } }