package com.dgex.offspring.update; import java.net.URI; import java.net.URISyntaxException; import org.apache.log4j.Logger; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.core.runtime.jobs.ProgressProvider; import org.eclipse.e4.ui.di.UISynchronize; import org.eclipse.e4.ui.workbench.IWorkbench; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.operations.ProvisioningJob; import org.eclipse.equinox.p2.operations.ProvisioningSession; import org.eclipse.equinox.p2.operations.Update; import org.eclipse.equinox.p2.operations.UpdateOperation; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressIndicator; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import com.dgex.offspring.dataprovider.service.IDataProviderPool; public class UpdateDialog extends TitleAreaDialog { static Logger logger = Logger.getLogger(UpdateDialog.class); static final String REPOSITORY_LOC = System.getProperty("UpdateJob.Repo", "file://home/dirk/OFFSPRING-REPO"); private Composite mainContainer = null; private Composite progressBarComposite; private Label messageLabel = null; private Button installButton; private ProgressIndicator progressBar = null; private final IProgressMonitor monitor; private Job updateJob = null; private final UISynchronize sync; private final IProvisioningAgent agent; private final Display display; private final IDataProviderPool pool; private final IWorkbench workbench; private boolean installUpdates = false; private boolean cancelUpdates = false; public UpdateDialog(Display display, Shell shell, UISynchronize sync, IProvisioningAgent agent, IDataProviderPool pool, IWorkbench workbench) { super(shell); this.monitor = new UpdateProgressMonitor(this, sync); this.sync = sync; this.agent = agent; this.display = display; this.pool = pool; this.workbench = workbench; } @Override public void create() { super.create(); setTitle("Software Update"); setMessage("Offspring software update service"); scheduleJob(); } public void setStatus(String status) { if (messageLabel != null && !messageLabel.isDisposed()) messageLabel.setText(status); } public void setError(String error) { if (messageLabel != null && !messageLabel.isDisposed()) messageLabel.setText("ERROR " + error); } public void showProgressIndicator() { if (progressBarComposite != null && !progressBarComposite.isDisposed()) { progressBarComposite.setVisible(true); GridDataFactory.fillDefaults().exclude(false) .applyTo(progressBarComposite); } if (mainContainer != null && !mainContainer.isDisposed()) mainContainer.layout(); } public void hideProgressIndicator() { if (progressBarComposite != null && !progressBarComposite.isDisposed()) { progressBarComposite.setVisible(false); progressBar.done(); GridDataFactory.fillDefaults().exclude(true) .applyTo(progressBarComposite); } if (mainContainer != null && !mainContainer.isDisposed()) mainContainer.layout(); } public void showInstallButton() { if (installButton != null && !installButton.isDisposed()) { installButton.setVisible(true); GridDataFactory.fillDefaults().exclude(false).applyTo(installButton); } if (mainContainer != null && !mainContainer.isDisposed()) mainContainer.layout(); } public void hideInstallButton() { if (installButton != null && !installButton.isDisposed()) { installButton.setVisible(false); GridDataFactory.fillDefaults().exclude(true).applyTo(installButton); } if (mainContainer != null && !mainContainer.isDisposed()) mainContainer.layout(); } public ProgressIndicator getProgressIndicator() { return progressBar; } @Override protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); getButton(IDialogConstants.OK_ID).setEnabled(false); } @Override protected void buttonPressed(int id) { if (id == IDialogConstants.CANCEL_ID) { cancelUpdates = true; if (updateJob != null) { updateJob.cancel(); } } super.buttonPressed(id); } @Override protected Control createDialogArea(Composite parent) { Composite container = (Composite) super.createDialogArea(parent); GridLayout layout = new GridLayout(1, false); layout.horizontalSpacing = 15; layout.marginTop = 10; layout.marginLeft = 10; GridData gd = new GridData(GridData.FILL, GridData.FILL, false, true); gd.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); mainContainer = new Composite(container, SWT.NONE); mainContainer.setLayoutData(gd); mainContainer.setLayout(layout); messageLabel = new Label(mainContainer, SWT.WRAP); messageLabel.setText("..."); GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL).grab(true, false) .applyTo(messageLabel); installButton = new Button(mainContainer, SWT.PUSH); installButton.setText("Install Updates"); installButton.setVisible(false); installButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { installUpdates = true; hideInstallButton(); } }); GridDataFactory.swtDefaults().exclude(true).applyTo(installButton); progressBarComposite = new Composite(mainContainer, SWT.NONE); progressBarComposite.setVisible(false); GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, true) .exclude(true).applyTo(progressBarComposite); GridLayoutFactory.fillDefaults().numColumns(1) .applyTo(progressBarComposite); progressBar = new ProgressIndicator(progressBarComposite, SWT.SMOOTH); GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL) .applyTo(progressBar); mainContainer.layout(); return container; } private void scheduleJob() { updateJob = createUpdateJob(); Job.getJobManager().setProgressProvider(new ProgressProvider() { @Override public IProgressMonitor createMonitor(Job job) { return monitor; } }); updateJob.schedule(); } private Job createUpdateJob() { return new Job("Update Job") { private final boolean doInstall = false; private boolean retry(String message) { return MessageDialog.openQuestion(getShell(), "Retry failed step?", message + "\n\nDo you want to retry?"); } @Override protected IStatus run(IProgressMonitor monitor) { final UpdateManager updateManager = new UpdateManager(monitor); sync.syncExec(new Runnable() { @Override public void run() { getButton(IDialogConstants.OK_ID).setEnabled(false); getButton(IDialogConstants.CANCEL_ID).setEnabled(true); } }); /* 1. Prepare update plumbing */ final ProvisioningSession session = new ProvisioningSession(agent); final UpdateOperation operation = new UpdateOperation(session); boolean success = updateManager.initialize(); if (!success) { while (!success && retry("Could not initialize update module")) { success = updateManager.initialize(); } if (!success) { sync.syncExec(new Runnable() { @Override public void run() { setError("Could not initialize update module"); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return Status.CANCEL_STATUS; } } URI uri = null; try { uri = new URI(REPOSITORY_LOC); } catch (final URISyntaxException e) { sync.syncExec(new Runnable() { @Override public void run() { setError("URI invalid " + e.getMessage()); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return Status.CANCEL_STATUS; } // set location of artifact and metadata repo operation.getProvisioningContext().setArtifactRepositories( new URI[] { uri }); operation.getProvisioningContext().setMetadataRepositories( new URI[] { uri }); /* 2. check for updates */ // run update checks causing I/O sync.syncExec(new Runnable() { @Override public void run() { setStatus("Checking for updates"); showProgressIndicator(); getButton(IDialogConstants.CANCEL_ID).setEnabled(true); } }); if (cancelUpdates) return Status.CANCEL_STATUS; final IStatus status = operation.resolveModal(monitor); if (cancelUpdates) return Status.CANCEL_STATUS; // failed to find updates (inform user and exit) if (status.getCode() == UpdateOperation.STATUS_NOTHING_TO_UPDATE) { sync.syncExec(new Runnable() { @Override public void run() { setStatus("No updates found for current installation"); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return Status.CANCEL_STATUS; } /* 3. Since there are updates we first scan the current installation */ // found updates if (status.isOK() && status.getSeverity() != IStatus.ERROR) { sync.syncExec(new Runnable() { @Override public void run() { String updates = ""; Update[] possibleUpdates = operation.getPossibleUpdates(); for (Update update : possibleUpdates) { updates += update + "\n"; } setStatus("These updates are available.\n" + updates + "\n\nDo you want to install these updates?"); showInstallButton(); hideProgressIndicator(); getButton(IDialogConstants.CANCEL_ID).setEnabled(true); } }); /* Wait for the user to press the install updates button */ try { while (true) { Thread.sleep(100); if (installUpdates || cancelUpdates) break; } } catch (InterruptedException e) { return Status.CANCEL_STATUS; } if (cancelUpdates) return Status.CANCEL_STATUS; sync.syncExec(new Runnable() { @Override public void run() { hideInstallButton(); setStatus("Creating backup of your install directory"); } }); success = updateManager.createFullBackup(); if (cancelUpdates) return Status.CANCEL_STATUS; if (!success) { while (!success && retry("Could not backup install directory")) { success = updateManager.createFullBackup(); } if (!success) { sync.syncExec(new Runnable() { @Override public void run() { setError("Something went wrong [Backup Install Directory]"); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return Status.CANCEL_STATUS; } } } else { sync.syncExec(new Runnable() { @Override public void run() { setError("Something went wrong '" + status.getMessage() + "'"); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return Status.CANCEL_STATUS; } final ProvisioningJob provisioningJob = operation .getProvisioningJob(monitor); // updates cannot run from within Eclipse IDE!!! if (provisioningJob == null) { System.err .println("Running update from within Eclipse IDE? This won't work!!!"); throw new NullPointerException(); } // register a job change listener to track // installation progress and notify user upon success provisioningJob.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { if (event.getResult().isOK()) { /* Have UpdateManager create a diff of installation files */ sync.syncExec(new Runnable() { @Override public void run() { hideProgressIndicator(); setStatus("Updates downloaded succesfully.\n" + "Please wait while we analyze downloaded files."); } }); boolean success = updateManager.analyzeChangedFiles(); if (!success) { while (!success && retry("Could not analyze updated files")) { success = updateManager.analyzeChangedFiles(); } if (!success) { sync.syncExec(new Runnable() { @Override public void run() { hideProgressIndicator(); setError("Something went wrong [Analyzing Updates Files]"); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return; } } /* Ensure that all jar files are in fact signed by us */ sync.syncExec(new Runnable() { @Override public void run() { setStatus("Verifying X.509 certificates of downloaded files."); hideProgressIndicator(); } }); success = updateManager.verifyJarCertificates(); if (!success) { while (!success && retry("Could not verify signed jar files")) { success = updateManager.verifyJarCertificates(); } if (!success) { sync.syncExec(new Runnable() { @Override public void run() { setError("Something went wrong [Verifying Jar Certificates]"); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return; } } /* Ensure that any unsigned updated file is not on our blacklist */ sync.syncExec(new Runnable() { @Override public void run() { setStatus("Verifying downloaded files."); hideProgressIndicator(); } }); success = updateManager.verifyUnsignedUpdates(); if (!success) { while (!success && retry("Could not verify other files")) { success = updateManager.verifyUnsignedUpdates(); } if (!success) { sync.syncExec(new Runnable() { @Override public void run() { setError("Something went wrong [Verifying Unsigned Updates]"); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return; } } /* * In case inconsistencies where found, we role back all updated * files and create an error log. The user is asked to send this * error log to DGEX for further support. */ if (!updateManager.isVerified()) { updateManager.createErrorLog(); sync.syncExec(new Runnable() { @Override public void run() { setError("We could not verify downloaded instalation files.\n" + "Your installation directory is backed up to [XXX] we advise you to delete your current " + "installation directory and continue in your backup directory."); hideProgressIndicator(); getButton(IDialogConstants.OK_ID).setEnabled(true); getButton(IDialogConstants.CANCEL_ID).setEnabled(false); } }); return; } sync.syncExec(new Runnable() { @Override public void run() { boolean restart = MessageDialog .openQuestion( getShell(), "Updates installed, restart?", "Updates have been installed successfully, you must restart for changes to have effect.\n" + "Do you want to restart Offspring?"); if (restart) { /* Before we restart we should go over all files in */ // Shutdown.execute(display, nxt, pool); // workbench.restart(); } } }); } super.done(event); } }); provisioningJob.schedule(); return Status.OK_STATUS; } }; } }