From 55182528cf30475351406a2964adf4b6063d0b7b Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Fri, 14 Oct 2011 14:49:29 +0100 Subject: [PATCH] Q: What does the plugin manager do? A: It manages plugins. --- .../api/plugins/BatchPluginCallback.java | 2 +- .../sf/briar/api/plugins/PluginManager.java | 16 + .../api/plugins/StreamPluginCallback.java | 5 +- .../api/transport/ConnectionDispatcher.java | 17 ++ api/net/sf/briar/api/ui/UiCallback.java | 25 ++ .../sf/briar/plugins/PluginManagerImpl.java | 289 ++++++++++++++++++ .../net/sf/briar/plugins/PluginsModule.java | 16 + components/net/sf/briar/plugins/Poller.java | 14 + .../net/sf/briar/plugins/PollerImpl.java | 78 +++++ .../plugins/bluetooth/BluetoothPlugin.java | 9 +- test/build.xml | 1 + .../briar/plugins/PluginManagerImplTest.java | 44 +++ 12 files changed, 510 insertions(+), 6 deletions(-) create mode 100644 api/net/sf/briar/api/plugins/PluginManager.java create mode 100644 api/net/sf/briar/api/transport/ConnectionDispatcher.java create mode 100644 api/net/sf/briar/api/ui/UiCallback.java create mode 100644 components/net/sf/briar/plugins/PluginManagerImpl.java create mode 100644 components/net/sf/briar/plugins/PluginsModule.java create mode 100644 components/net/sf/briar/plugins/Poller.java create mode 100644 components/net/sf/briar/plugins/PollerImpl.java create mode 100644 test/net/sf/briar/plugins/PluginManagerImplTest.java diff --git a/api/net/sf/briar/api/plugins/BatchPluginCallback.java b/api/net/sf/briar/api/plugins/BatchPluginCallback.java index 8c964ed901..8aae562002 100644 --- a/api/net/sf/briar/api/plugins/BatchPluginCallback.java +++ b/api/net/sf/briar/api/plugins/BatchPluginCallback.java @@ -12,5 +12,5 @@ public interface BatchPluginCallback extends PluginCallback { void readerCreated(BatchTransportReader r); - void writerCreated(ContactId contactId, BatchTransportWriter w); + void writerCreated(ContactId c, BatchTransportWriter w); } diff --git a/api/net/sf/briar/api/plugins/PluginManager.java b/api/net/sf/briar/api/plugins/PluginManager.java new file mode 100644 index 0000000000..379c07bb97 --- /dev/null +++ b/api/net/sf/briar/api/plugins/PluginManager.java @@ -0,0 +1,16 @@ +package net.sf.briar.api.plugins; + +public interface PluginManager { + + /** + * Starts all the plugins the manager knows about and returns the number of + * plugins successfully started. + */ + int startPlugins(); + + /** + * Stops all the plugins started by startPlugins() and returns the number + * of plugins successfully stopped. + */ + int stopPlugins(); +} diff --git a/api/net/sf/briar/api/plugins/StreamPluginCallback.java b/api/net/sf/briar/api/plugins/StreamPluginCallback.java index 74bd4e11a1..adabd8e17b 100644 --- a/api/net/sf/briar/api/plugins/StreamPluginCallback.java +++ b/api/net/sf/briar/api/plugins/StreamPluginCallback.java @@ -9,8 +9,7 @@ import net.sf.briar.api.transport.StreamTransportConnection; */ public interface StreamPluginCallback extends PluginCallback { - void incomingConnectionCreated(StreamTransportConnection c); + void incomingConnectionCreated(StreamTransportConnection s); - void outgoingConnectionCreated(ContactId contactId, - StreamTransportConnection c); + void outgoingConnectionCreated(ContactId c, StreamTransportConnection s); } diff --git a/api/net/sf/briar/api/transport/ConnectionDispatcher.java b/api/net/sf/briar/api/transport/ConnectionDispatcher.java new file mode 100644 index 0000000000..9d21d65bf9 --- /dev/null +++ b/api/net/sf/briar/api/transport/ConnectionDispatcher.java @@ -0,0 +1,17 @@ +package net.sf.briar.api.transport; + +import net.sf.briar.api.ContactId; +import net.sf.briar.api.TransportId; + +public interface ConnectionDispatcher { + + void dispatchReader(TransportId t, BatchTransportReader r); + + void dispatchWriter(TransportId t, ContactId c, + BatchTransportWriter w); + + void dispatchIncomingConnection(TransportId t, StreamTransportConnection s); + + void dispatchOutgoingConnection(TransportId t, ContactId c, + StreamTransportConnection s); +} diff --git a/api/net/sf/briar/api/ui/UiCallback.java b/api/net/sf/briar/api/ui/UiCallback.java new file mode 100644 index 0000000000..d241eadb37 --- /dev/null +++ b/api/net/sf/briar/api/ui/UiCallback.java @@ -0,0 +1,25 @@ +package net.sf.briar.api.ui; + +public interface UiCallback { + + /** + * Presents the user with a choice among two or more named options and + * returns the user's response. The message may consist of a translatable + * format string and arguments. + * @return An index into the array of options indicating the user's choice, + * or -1 if the user cancelled the choice. + */ + int showChoice(String[] options, String... message); + + /** + * Asks the user to confirm an action and returns the user's response. The + * message may consist of a translatable format string and arguments. + */ + boolean showConfirmationMessage(String... message); + + /** + * Shows a message to the user. The message may consist of a translatable + * format string and arguments. + */ + void showMessage(String... message); +} diff --git a/components/net/sf/briar/plugins/PluginManagerImpl.java b/components/net/sf/briar/plugins/PluginManagerImpl.java new file mode 100644 index 0000000000..adb10ed2c3 --- /dev/null +++ b/components/net/sf/briar/plugins/PluginManagerImpl.java @@ -0,0 +1,289 @@ +package net.sf.briar.plugins; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sf.briar.api.ContactId; +import net.sf.briar.api.TransportConfig; +import net.sf.briar.api.TransportId; +import net.sf.briar.api.TransportProperties; +import net.sf.briar.api.db.DatabaseComponent; +import net.sf.briar.api.db.DbException; +import net.sf.briar.api.plugins.BatchPlugin; +import net.sf.briar.api.plugins.BatchPluginCallback; +import net.sf.briar.api.plugins.BatchPluginFactory; +import net.sf.briar.api.plugins.Plugin; +import net.sf.briar.api.plugins.PluginCallback; +import net.sf.briar.api.plugins.PluginManager; +import net.sf.briar.api.plugins.StreamPlugin; +import net.sf.briar.api.plugins.StreamPluginCallback; +import net.sf.briar.api.plugins.StreamPluginFactory; +import net.sf.briar.api.protocol.TransportUpdate; +import net.sf.briar.api.transport.BatchTransportReader; +import net.sf.briar.api.transport.BatchTransportWriter; +import net.sf.briar.api.transport.ConnectionDispatcher; +import net.sf.briar.api.transport.StreamTransportConnection; +import net.sf.briar.api.ui.UiCallback; + +import com.google.inject.Inject; + +class PluginManagerImpl implements PluginManager { + + private static final Logger LOG = + Logger.getLogger(PluginManagerImpl.class.getName()); + + private static final String[] BATCH_FACTORIES = new String[] { + "net.sf.briar.plugins.file.RemovableDrivePluginFactory" + }; + + private static final String[] STREAM_FACTORIES = new String[] { + "net.sf.briar.plugins.bluetooth.BluetoothPluginFactory", + "net.sf.briar.plugins.socket.SimpleSocketPluginFactory" + }; + + private final Executor executor; + private final DatabaseComponent db; + private final Poller poller; + private final ConnectionDispatcher dispatcher; + private final UiCallback uiCallback; + private final List<BatchPlugin> batchPlugins; + private final List<StreamPlugin> streamPlugins; + + @Inject + PluginManagerImpl(Executor executor, DatabaseComponent db, Poller poller, + ConnectionDispatcher dispatcher, UiCallback uiCallback) { + this.executor = executor; + this.db = db; + this.poller = poller; + this.dispatcher = dispatcher; + this.uiCallback = uiCallback; + batchPlugins = new ArrayList<BatchPlugin>(); + streamPlugins = new ArrayList<StreamPlugin>(); + } + + public synchronized int startPlugins() { + Set<TransportId> ids = new HashSet<TransportId>(); + // Instantiate and start the batch plugins + for(String s : BATCH_FACTORIES) { + try { + Class<?> c = Class.forName(s); + BatchPluginFactory factory = + (BatchPluginFactory) c.newInstance(); + BatchCallback callback = new BatchCallback(); + BatchPlugin plugin = factory.createPlugin(executor, callback); + if(plugin == null) { + if(LOG.isLoggable(Level.INFO)) + LOG.info(factory.getClass().getSimpleName() + + " did not create a plugin"); + continue; + } + TransportId id = plugin.getId(); + if(!ids.add(id)) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Duplicate transport ID: " + id); + continue; + } + callback.setId(id); + plugin.start(); + batchPlugins.add(plugin); + } catch(ClassCastException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + continue; + } catch(Exception e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + continue; + } + } + // Instantiate and start the stream plugins + for(String s : STREAM_FACTORIES) { + try { + Class<?> c = Class.forName(s); + StreamPluginFactory factory = + (StreamPluginFactory) c.newInstance(); + StreamCallback callback = new StreamCallback(); + StreamPlugin plugin = factory.createPlugin(executor, callback); + if(plugin == null) { + if(LOG.isLoggable(Level.INFO)) + LOG.info(factory.getClass().getSimpleName() + + " did not create a plugin"); + continue; + } + TransportId id = plugin.getId(); + if(!ids.add(id)) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Duplicate transport ID: " + id); + continue; + } + callback.setId(id); + plugin.start(); + streamPlugins.add(plugin); + } catch(ClassCastException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + continue; + } catch(Exception e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + continue; + } + } + // Start the poller + List<Plugin> plugins = new ArrayList<Plugin>(); + plugins.addAll(batchPlugins); + plugins.addAll(streamPlugins); + poller.startPolling(plugins); + // Return the number of plugins started + return batchPlugins.size() + streamPlugins.size(); + } + + public synchronized int stopPlugins() { + int stopped = 0; + // Stop the batch plugins + for(BatchPlugin plugin : batchPlugins) { + try { + plugin.stop(); + stopped++; + } catch(IOException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } + batchPlugins.clear(); + // Stop the stream plugins + for(StreamPlugin plugin : streamPlugins) { + try { + plugin.stop(); + stopped++; + } catch(IOException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } + streamPlugins.clear(); + // Return the number of plugins stopped + return stopped; + } + + private abstract class PluginCallbackImpl implements PluginCallback { + + protected volatile TransportId id = null; + + protected void setId(TransportId id) { + assert this.id == null; + this.id = id; + } + + public TransportConfig getConfig() { + assert id != null; + try { + return db.getConfig(id); + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + return new TransportConfig(); + } + } + + public TransportProperties getLocalProperties() { + assert id != null; + try { + TransportProperties p = db.getLocalTransports().get(id); + return p == null ? new TransportProperties() : p; + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + return new TransportProperties(); + } + } + + public Map<ContactId, TransportProperties> getRemoteProperties() { + assert id != null; + try { + return db.getRemoteProperties(id); + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + return Collections.emptyMap(); + } + } + + public void setConfig(TransportConfig c) { + assert id != null; + try { + db.setConfig(id, c); + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } + + public void setLocalProperties(TransportProperties p) { + assert id != null; + if(p.size() > TransportUpdate.MAX_PROPERTIES_PER_PLUGIN) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Plugin " + id + " set too many properties"); + return; + } + for(String s : p.keySet()) { + if(s.length() > TransportUpdate.MAX_KEY_OR_VALUE_LENGTH) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Plugin " + id + " set long key: " + s); + return; + } + } + for(String s : p.values()) { + if(s.length() > TransportUpdate.MAX_KEY_OR_VALUE_LENGTH) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Plugin " + id + " set long value: " + s); + return; + } + } + try { + db.setLocalProperties(id, p); + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } + + public int showChoice(String[] options, String... message) { + return uiCallback.showChoice(options, message); + } + + public boolean showConfirmationMessage(String... message) { + return uiCallback.showConfirmationMessage(message); + } + + public void showMessage(String... message) { + uiCallback.showMessage(message); + } + } + + private class BatchCallback extends PluginCallbackImpl + implements BatchPluginCallback { + + public void readerCreated(BatchTransportReader r) { + assert id != null; + dispatcher.dispatchReader(id, r); + } + + public void writerCreated(ContactId c, BatchTransportWriter w) { + assert id != null; + dispatcher.dispatchWriter(id, c, w); + } + } + + private class StreamCallback extends PluginCallbackImpl + implements StreamPluginCallback { + + public void incomingConnectionCreated(StreamTransportConnection s) { + assert id != null; + dispatcher.dispatchIncomingConnection(id, s); + } + + public void outgoingConnectionCreated(ContactId c, + StreamTransportConnection s) { + assert id != null; + dispatcher.dispatchOutgoingConnection(id, c, s); + } + } +} \ No newline at end of file diff --git a/components/net/sf/briar/plugins/PluginsModule.java b/components/net/sf/briar/plugins/PluginsModule.java new file mode 100644 index 0000000000..5cb124da21 --- /dev/null +++ b/components/net/sf/briar/plugins/PluginsModule.java @@ -0,0 +1,16 @@ +package net.sf.briar.plugins; + +import net.sf.briar.api.plugins.PluginManager; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; + +public class PluginsModule extends AbstractModule { + + @Override + protected void configure() { + bind(PluginManager.class).to( + PluginManagerImpl.class).in(Singleton.class); + bind(Poller.class).to(PollerImpl.class); + } +} diff --git a/components/net/sf/briar/plugins/Poller.java b/components/net/sf/briar/plugins/Poller.java new file mode 100644 index 0000000000..0c349722aa --- /dev/null +++ b/components/net/sf/briar/plugins/Poller.java @@ -0,0 +1,14 @@ +package net.sf.briar.plugins; + +import java.util.Collection; + +import net.sf.briar.api.plugins.Plugin; + +interface Poller { + + /** Starts a new thread to poll the given collection of plugins. */ + void startPolling(Collection<Plugin> plugins); + + /** Tells the poller thread to exit. */ + void stopPolling(); +} diff --git a/components/net/sf/briar/plugins/PollerImpl.java b/components/net/sf/briar/plugins/PollerImpl.java new file mode 100644 index 0000000000..5905fd243f --- /dev/null +++ b/components/net/sf/briar/plugins/PollerImpl.java @@ -0,0 +1,78 @@ +package net.sf.briar.plugins; + +import java.util.Collection; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sf.briar.api.plugins.Plugin; + +class PollerImpl implements Poller, Runnable { + + private static final Logger LOG = + Logger.getLogger(PollerImpl.class.getName()); + + private final SortedSet<PollTime> pollTimes = new TreeSet<PollTime>(); + + public synchronized void startPolling(Collection<Plugin> plugins) { + for(Plugin plugin : plugins) schedule(plugin); + new Thread(this).start(); + } + + private synchronized void schedule(Plugin plugin) { + if(plugin.shouldPoll()) { + long now = System.currentTimeMillis(); + long interval = plugin.getPollingInterval(); + pollTimes.add(new PollTime(now + interval, plugin)); + } + } + + public synchronized void stopPolling() { + pollTimes.clear(); + notifyAll(); + } + + public void run() { + while(true) { + synchronized(this) { + if(pollTimes.isEmpty()) return; + PollTime p = pollTimes.first(); + long now = System.currentTimeMillis(); + if(now <= p.time) { + pollTimes.remove(p); + try { + p.plugin.poll(); + } catch(RuntimeException e) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Plugin " + p.plugin.getId() + " " + e); + } + schedule(p.plugin); + } else { + try { + wait(p.time - now); + } catch(InterruptedException ignored) {} + } + } + } + } + + private static class PollTime implements Comparable<PollTime> { + + private final long time; + private final Plugin plugin; + + private PollTime(long time, Plugin plugin) { + this.time = time; + this.plugin = plugin; + } + + public int compareTo(PollTime p) { + if(time < p.time) return -1; + if(time > p.time) return 1; + if(plugin.getId().getInt() < p.plugin.getId().getInt()) return -1; + if(plugin.getId().getInt() > p.plugin.getId().getInt()) return 1; + return 0; + } + } +} diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java index b906dc073d..d5db89a720 100644 --- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java +++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java @@ -63,8 +63,13 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin { } } catch(UnsatisfiedLinkError e) { // On Linux the user may need to install libbluetooth-dev - if(OsUtils.isLinux()) - callback.showMessage("BLUETOOTH_INSTALL LIBS"); + if(OsUtils.isLinux()) { + executor.execute(new Runnable() { + public void run() { + callback.showMessage("BLUETOOTH_INSTALL_LIBS"); + } + }); + } throw new IOException(e.getMessage()); } executor.execute(createBinder()); diff --git a/test/build.xml b/test/build.xml index b5e2813b04..b0d0118ed9 100644 --- a/test/build.xml +++ b/test/build.xml @@ -26,6 +26,7 @@ <test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/> + <test name='net.sf.briar.plugins.PluginManagerImplTest'/> <test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/> <test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/> <test name='net.sf.briar.plugins.file.PollingRemovableDriveMonitorTest'/> diff --git a/test/net/sf/briar/plugins/PluginManagerImplTest.java b/test/net/sf/briar/plugins/PluginManagerImplTest.java new file mode 100644 index 0000000000..f8260fa81a --- /dev/null +++ b/test/net/sf/briar/plugins/PluginManagerImplTest.java @@ -0,0 +1,44 @@ +package net.sf.briar.plugins; + +import java.util.Map; +import java.util.concurrent.Executor; + +import junit.framework.TestCase; +import net.sf.briar.api.TransportId; +import net.sf.briar.api.TransportProperties; +import net.sf.briar.api.db.DatabaseComponent; +import net.sf.briar.api.transport.ConnectionDispatcher; +import net.sf.briar.api.ui.UiCallback; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Test; + +public class PluginManagerImplTest extends TestCase { + + @Test + public void testStartAndStop() throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Map<?, ?> localTransports = context.mock(Map.class); + final ConnectionDispatcher dispatcher = + context.mock(ConnectionDispatcher.class); + final UiCallback uiCallback = context.mock(UiCallback.class); + context.checking(new Expectations() {{ + allowing(db).getLocalTransports(); + will(returnValue(localTransports)); + allowing(localTransports).get(with(any(TransportId.class))); + will(returnValue(new TransportProperties())); + allowing(db).getRemoteProperties(with(any(TransportId.class))); + will(returnValue(new TransportProperties())); + }}); + Executor executor = new ImmediateExecutor(); + Poller poller = new PollerImpl(); + PluginManagerImpl p = + new PluginManagerImpl(executor, db, poller, dispatcher, uiCallback); + // The Bluetooth plugin will not start without a Bluetooth device, so + // we expect two plugins to be started + assertEquals(2, p.startPlugins()); + assertEquals(2, p.stopPlugins()); + } +} -- GitLab