Add dialog to notify user of discovery agents not loaded because prerequisites not satisfied. Bug 10486
authorChad Peckham <chad.peckham@nokia.com>
Fri, 29 Jan 2010 15:42:36 -0600
changeset 849 d8886f16bea3
parent 847 6f65d25899ca
child 850 cd20cfc0713f
Add dialog to notify user of discovery agents not loaded because prerequisites not satisfied. Bug 10486
connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/RemoteConnectionsActivator.java
connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/internal/api/IDeviceDiscoveryAgent.java
connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/internal/ui/DeviceDiscoveryPrequisiteErrorDialog.java
--- a/connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/RemoteConnectionsActivator.java	Fri Jan 29 15:11:48 2010 -0600
+++ b/connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/RemoteConnectionsActivator.java	Fri Jan 29 15:42:36 2010 -0600
@@ -26,6 +26,7 @@
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.viewers.IFilter;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.osgi.framework.BundleContext;
@@ -33,8 +34,11 @@
 import com.nokia.carbide.remoteconnections.interfaces.IConnectionTypeProvider;
 import com.nokia.carbide.remoteconnections.interfaces.IConnectionsManager;
 import com.nokia.carbide.remoteconnections.internal.api.IDeviceDiscoveryAgent;
+import com.nokia.carbide.remoteconnections.internal.api.IDeviceDiscoveryAgent.IPrerequisiteStatus;
 import com.nokia.carbide.remoteconnections.internal.registry.Registry;
+import com.nokia.carbide.remoteconnections.internal.ui.DeviceDiscoveryPrequisiteErrorDialog;
 import com.nokia.cpp.internal.api.utils.core.Logging;
+import com.nokia.cpp.internal.api.utils.ui.WorkbenchUtils;
 
 /**
  * The activator class controls the plug-in life cycle
@@ -51,6 +55,8 @@
 	private static RemoteConnectionsActivator plugin;
 
 	private Collection<IDeviceDiscoveryAgent> discoveryAgents;
+	private boolean ignoreAgentLoadErrors = false;
+	private static final String IGNORE_AGENT_LOAD_ERRORS_KEY = "ignoreAgentLoadErrors"; //$NON-NLS-1$
 
 	/**
 	 * The constructor
@@ -64,17 +70,74 @@
 		Registry instance = Registry.instance();
 		instance.loadExtensions();
 		instance.loadConnections();
-		loadAndStartDeviceDiscoveryAgents();
+
+		loadIgnoreAgentLoadErrorsFlag();
+		if (!ignoreAgentLoadErrors) {
+			checkPrerequisites();
+			// loading done in checkPrerequisites after load errors dealt with
+		} else {
+			// if we ignore the load errors
+			// go ahead and load anyway
+			loadAndStartDeviceDiscoveryAgents();
+		}
+	}
+
+	private void loadIgnoreAgentLoadErrorsFlag() {
+		ignoreAgentLoadErrors = getPreferenceStore().getBoolean(IGNORE_AGENT_LOAD_ERRORS_KEY);
+	}
+
+	private void checkPrerequisites() {
+		final Collection<String> agentNames = new ArrayList<String>();
+		final Collection<IPrerequisiteStatus> agentStatuses = new ArrayList<IPrerequisiteStatus>();
+		// load the extensions just to check statuses
+		// later we'll load them for real
+		Collection<IDeviceDiscoveryAgent> agents = new ArrayList<IDeviceDiscoveryAgent>();
+		
+		loadExtensions(DISCOVERY_AGENT_EXTENSION, null, agents, null);
+		
+		for (IDeviceDiscoveryAgent agent : agents) {
+			IPrerequisiteStatus status = agent.getPrerequisiteStatus();
+			if (!status.isOK()) {
+				agentNames.add(agent.getDisplayName());
+				agentStatuses.add(status);
+			}
+		}
+
+		if (!agentNames.isEmpty()) {
+			Display.getDefault().asyncExec(new Runnable() {
+
+				public void run() {
+					DeviceDiscoveryPrequisiteErrorDialog dlg = new DeviceDiscoveryPrequisiteErrorDialog(WorkbenchUtils.getSafeShell());
+					IPrerequisiteStatus[] statuses = (IPrerequisiteStatus[]) agentStatuses.toArray(new IPrerequisiteStatus[agentStatuses.size()]);
+					String[] names = agentNames.toArray(new String[agentNames.size()]);
+					for (int i = 0; i < names.length; i++) {
+						dlg.addAgentData(names[i], statuses[i].getErrorText(), statuses[i].getURL());
+					}
+					dlg.open();
+					ignoreAgentLoadErrors = dlg.isDontBotherMeOn();
+					dlg.close();
+					
+					// now load and start agents for real
+					loadAndStartDeviceDiscoveryAgents();
+				}
+			});
+		}
 	}
 
 	public void stop(BundleContext context) throws Exception {
 		stopDeviceDiscoveryAgents();
 		Registry.instance().storeConnections();
 		Registry.instance().disposeConnections();
+		storeIgnoreAgentLoadErrorsFlag();
 		plugin = null;
 		super.stop(context);
 	}
 
+	private void storeIgnoreAgentLoadErrorsFlag() {
+		getPreferenceStore().setValue(IGNORE_AGENT_LOAD_ERRORS_KEY, ignoreAgentLoadErrors);
+		savePluginPreferences();
+	}
+
 	/**
 	 * Returns the shared instance
 	 *
--- a/connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/internal/api/IDeviceDiscoveryAgent.java	Fri Jan 29 15:11:48 2010 -0600
+++ b/connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/internal/api/IDeviceDiscoveryAgent.java	Fri Jan 29 15:42:36 2010 -0600
@@ -28,6 +28,31 @@
 public interface IDeviceDiscoveryAgent {
 
 	/**
+	 * An interface for discovery agents to implement that the manager uses
+	 * before loading the agent to get errors about required components.
+	 * <p>
+	 * Every discovery agent must implement this interface.
+	 */
+	public interface IPrerequisiteStatus {
+		/**
+		 * Is the status OK?
+		 * @return boolean 
+		 */
+		boolean isOK();
+		/**
+		 * If status is not OK, return error message text.
+		 * @return String
+		 */
+		String getErrorText();
+		/**
+		 * Optionally return a URL for user to browse to for more information on error.
+		 * @return URL
+		 */
+		URL getURL();
+		
+	}
+
+	/**
 	 * Starts agent. Once started, runs until stopped.
 	 * @throws CoreException
 	 */
@@ -46,6 +71,22 @@
 	 */
 	URL getInformation();
 	
+	/**
+	 * Returns a display name for the particular discovery agent in case we need to
+	 * let the user know errors from the agents.
+	 * @return String
+	 */
+	String getDisplayName();
+	
+	/**
+	 * Manager will call this to get any status of prerequisites not satisfied before
+	 * the manager calls start(). The agent should check its prerequisites at this time.
+	 * <p>
+	 * If the agent has no prerequisites return a status of OK.
+	 * @return IPrerequisiteStatus
+	 */
+	IPrerequisiteStatus getPrerequisiteStatus();
+	
 	// In addition, there may need to be an additional API to do a deeper form of discovery for
 	// connection mechanisms that require pairing (like BT or Wifi). In these cases, normal discovery
 	// will probably be for already paired devices, however, the user will want to discover all 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/connectivity/com.nokia.carbide.remoteConnections/src/com/nokia/carbide/remoteconnections/internal/ui/DeviceDiscoveryPrequisiteErrorDialog.java	Fri Jan 29 15:42:36 2010 -0600
@@ -0,0 +1,230 @@
+/*
+* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+package com.nokia.carbide.remoteconnections.internal.ui;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.ListViewer;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.window.IShellProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+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.Event;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+
+import com.nokia.carbide.remoteconnections.RemoteConnectionsActivator;
+
+public class DeviceDiscoveryPrequisiteErrorDialog extends Dialog {
+
+	private class AgentItem {
+		public String agentName;
+		public String agentErrorText;
+		public URL agentLocation;
+		
+		AgentItem(String name, String text, URL location) {
+			agentName = name;
+			agentErrorText = text;
+			agentLocation = location;
+			// if location is not null and error text doesn't contain href
+			//  then do it here
+			if (agentLocation != null && !agentErrorText.contains("href=")) {
+				String msg = String.format("%s - For more information go to: <a href=\"%s\">%s</a>",
+						agentErrorText, location.toString(), location.toString());
+				agentErrorText = msg;
+			}
+		}
+	}
+	
+	private Collection<AgentItem> agentList = new ArrayList<AgentItem>();
+	private boolean isDontBotherMeOn = false;
+	private ListViewer agentListViewer;
+	private Link errorText;
+	private Button dontBotherMeCheckBox;
+
+	/**
+	 * @param parentShell
+	 */
+	public DeviceDiscoveryPrequisiteErrorDialog(Shell parentShell) {
+		super(parentShell);
+		agentList.clear();
+	}
+
+	/**
+	 * @param parentShell
+	 */
+	public DeviceDiscoveryPrequisiteErrorDialog(IShellProvider parentShell) {
+		super(parentShell);
+		agentList.clear();
+	}
+
+	public void addAgentData(String name, String errorText, URL location) {
+		agentList.add(new AgentItem(name, errorText, location));
+	}
+
+	public boolean isDontBotherMeOn() {
+		return isDontBotherMeOn;
+	}
+
+	@Override
+	protected void createButtonsForButtonBar(Composite parent) {
+		// OK button == "Close"
+		// no Cancel button
+		createButton(parent, IDialogConstants.OK_ID, IDialogConstants.CLOSE_LABEL, true);
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		initializeDialogUnits(parent);
+		
+		Composite container = new Composite(parent, SWT.NONE);
+		GridLayout layout = new GridLayout(1, true);
+		container.setLayout(layout);
+		container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+	
+		// Message at top
+		Text topMessage = new Text(container, SWT.MULTI | SWT.WRAP);
+		topMessage.setText("At least one device discovery agent had load errors that prevent it from discovering connections to devices. Select one to get more information about its error.");
+		topMessage.setEditable(false);
+		topMessage.setDoubleClickEnabled(false);
+		GridData topMsgData = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+		topMsgData.heightHint = 48;
+		topMessage.setLayoutData(topMsgData);
+		topMessage.setToolTipText("Select an agent for more information about load errors.");
+
+		// next two panes can be resized with a sash form
+		SashForm sashForm = new SashForm(container, SWT.VERTICAL);
+		GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
+		sashForm.setLayoutData(gridData);
+		sashForm.setToolTipText("Slide to adjust pane size above and below to see more text.");
+		
+
+		// this pane lists all the agent display names
+		agentListViewer = new ListViewer(sashForm, SWT.V_SCROLL | SWT.BORDER);
+		agentListViewer.setContentProvider(new ArrayContentProvider());
+		agentListViewer.setLabelProvider(new LabelProvider() {
+
+			@Override
+			public String getText(Object element) {
+				return ((AgentItem)element).agentName;
+			}
+			
+		});
+		agentListViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+			public void selectionChanged(SelectionChangedEvent event) {
+				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
+				AgentItem item = (AgentItem) selection.getFirstElement();
+				errorText.setText(item.agentErrorText);				
+			}
+			
+		});
+		agentListViewer.setInput(agentList);
+
+		// pane to view the information about the selected agent
+		errorText = new Link(sashForm, SWT.V_SCROLL | SWT.BORDER | SWT.WRAP);
+		errorText.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+		errorText.setToolTipText("Error message for the selected agent above");
+		errorText.addListener(SWT.Selection, new Listener() {
+
+			public void handleEvent(Event event) {
+				// Launch an external browser
+				String siteText = event.text;
+				IWorkbench workbench = PlatformUI.getWorkbench();
+				try {
+					IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser();
+					browser.openURL(new URL(siteText));
+				} catch (Exception e) {
+					RemoteConnectionsActivator.logError(e);
+				}
+			}
+			
+		});
+		
+		// add initial weights to the above two panes
+		sashForm.setWeights(new int[] {150,200});
+
+		// now the don't bother me check box
+		dontBotherMeCheckBox = new Button(container, SWT.CHECK);
+		dontBotherMeCheckBox.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, true, false));
+		dontBotherMeCheckBox.setText("Don't bother me again.");
+		dontBotherMeCheckBox.setToolTipText("Check this to ignore further discovery agent load errors");
+		dontBotherMeCheckBox.setSelection(isDontBotherMeOn);
+		dontBotherMeCheckBox.addSelectionListener(new SelectionAdapter() {
+
+			public void widgetSelected(SelectionEvent e) {
+				isDontBotherMeOn = dontBotherMeCheckBox.getSelection();
+			}
+
+		});
+		
+		// now finish by selecting the top most agent in the list
+		// and bringing it into view
+		Object o = agentListViewer.getElementAt(0);
+		if (o != null)
+			agentListViewer.setSelection(new StructuredSelection(o));
+		
+		ISelection selection = agentListViewer.getSelection();
+		if (selection != null && !selection.isEmpty()) {
+			agentListViewer.reveal(selection);
+		}
+		
+		return container;
+	}
+
+	@Override
+	protected Point getInitialSize() {
+		return new Point(400,400);
+	}
+
+	@Override
+	protected void okPressed() {
+		// TODO Auto-generated method stub
+		super.okPressed();
+	}
+
+	@Override
+	protected void configureShell(Shell newShell) {
+		super.configureShell(newShell);
+		// set our title to the dialog
+		newShell.setText("Device Discovery Load Errors");
+	}
+}