mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Q: What does the plugin manager do? A: It manages plugins.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
16
api/net/sf/briar/api/plugins/PluginManager.java
Normal file
16
api/net/sf/briar/api/plugins/PluginManager.java
Normal file
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
17
api/net/sf/briar/api/transport/ConnectionDispatcher.java
Normal file
17
api/net/sf/briar/api/transport/ConnectionDispatcher.java
Normal file
@@ -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);
|
||||
}
|
||||
25
api/net/sf/briar/api/ui/UiCallback.java
Normal file
25
api/net/sf/briar/api/ui/UiCallback.java
Normal file
@@ -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);
|
||||
}
|
||||
289
components/net/sf/briar/plugins/PluginManagerImpl.java
Normal file
289
components/net/sf/briar/plugins/PluginManagerImpl.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
components/net/sf/briar/plugins/PluginsModule.java
Normal file
16
components/net/sf/briar/plugins/PluginsModule.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
14
components/net/sf/briar/plugins/Poller.java
Normal file
14
components/net/sf/briar/plugins/Poller.java
Normal file
@@ -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();
|
||||
}
|
||||
78
components/net/sf/briar/plugins/PollerImpl.java
Normal file
78
components/net/sf/briar/plugins/PollerImpl.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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'/>
|
||||
|
||||
44
test/net/sf/briar/plugins/PluginManagerImplTest.java
Normal file
44
test/net/sf/briar/plugins/PluginManagerImplTest.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user