mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Moved desktop-specific code into a separate project (other task #34).
This commit is contained in:
@@ -2,7 +2,6 @@ package net.sf.briar.lifecycle;
|
||||
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
import net.sf.briar.api.lifecycle.ShutdownManager;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Singleton;
|
||||
@@ -12,8 +11,7 @@ public class LifecycleModule extends AbstractModule {
|
||||
protected void configure() {
|
||||
bind(LifecycleManager.class).to(
|
||||
LifecycleManagerImpl.class).in(Singleton.class);
|
||||
if(OsUtils.isWindows())
|
||||
bind(ShutdownManager.class).to(WindowsShutdownManagerImpl.class);
|
||||
else bind(ShutdownManager.class).to(ShutdownManagerImpl.class);
|
||||
bind(ShutdownManager.class).to(
|
||||
ShutdownManagerImpl.class).in(Singleton.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package net.sf.briar.lifecycle;
|
||||
|
||||
import static com.sun.jna.Library.OPTION_FUNCTION_MAPPER;
|
||||
import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.util.OsUtils;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.platform.win32.WinDef.HINSTANCE;
|
||||
import com.sun.jna.platform.win32.WinDef.HMENU;
|
||||
import com.sun.jna.platform.win32.WinDef.HWND;
|
||||
import com.sun.jna.platform.win32.WinDef.LPARAM;
|
||||
import com.sun.jna.platform.win32.WinDef.LRESULT;
|
||||
import com.sun.jna.platform.win32.WinDef.WPARAM;
|
||||
import com.sun.jna.platform.win32.WinUser.MSG;
|
||||
import com.sun.jna.win32.StdCallLibrary;
|
||||
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;
|
||||
import com.sun.jna.win32.W32APIFunctionMapper;
|
||||
import com.sun.jna.win32.W32APITypeMapper;
|
||||
|
||||
class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WindowsShutdownManagerImpl.class.getName());
|
||||
|
||||
private static final int WM_QUERYENDSESSION = 17;
|
||||
private static final int GWL_WNDPROC = -4;
|
||||
private static final int WS_MINIMIZE = 0x20000000;
|
||||
|
||||
private final Map<String, Object> options;
|
||||
|
||||
private boolean initialised = false; // Locking: this
|
||||
|
||||
WindowsShutdownManagerImpl() {
|
||||
// Use the Unicode versions of Win32 API calls
|
||||
Map<String, Object> m = new HashMap<String, Object>();
|
||||
m.put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
|
||||
m.put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
|
||||
options = Collections.unmodifiableMap(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int addShutdownHook(Runnable r) {
|
||||
if(!initialised) initialise();
|
||||
return super.addShutdownHook(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Thread createThread(Runnable r) {
|
||||
return new StartOnce(r);
|
||||
}
|
||||
|
||||
// Locking: this
|
||||
private void initialise() {
|
||||
if(OsUtils.isWindows()) {
|
||||
new EventLoop().start();
|
||||
} else {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Windows shutdown manager used on non-Windows OS");
|
||||
}
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
synchronized void runShutdownHooks() {
|
||||
boolean interrupted = false;
|
||||
// Start each hook in its own thread
|
||||
for(Thread hook : hooks.values()) hook.start();
|
||||
// Wait for all the hooks to finish
|
||||
for(Thread hook : hooks.values()) {
|
||||
try {
|
||||
hook.join();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while running shutdown hooks");
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
if(interrupted) Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
private class EventLoop extends Thread {
|
||||
|
||||
private EventLoop() {
|
||||
super("EventLoop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Load user32.dll
|
||||
final User32 user32 = (User32) Native.loadLibrary("user32",
|
||||
User32.class, options);
|
||||
// Create a callback to handle the WM_QUERYENDSESSION message
|
||||
WindowProc proc = new WindowProc() {
|
||||
public LRESULT callback(HWND hwnd, int msg, WPARAM wp,
|
||||
LPARAM lp) {
|
||||
if(msg == WM_QUERYENDSESSION) {
|
||||
// It's safe to delay returning from this message
|
||||
runShutdownHooks();
|
||||
}
|
||||
// Pass the message to the default window procedure
|
||||
return user32.DefWindowProc(hwnd, msg, wp, lp);
|
||||
}
|
||||
};
|
||||
// Create a native window
|
||||
HWND hwnd = user32.CreateWindowEx(0, "STATIC", "", WS_MINIMIZE,
|
||||
0, 0, 0, 0, null, null, null, null);
|
||||
// Register the callback
|
||||
try {
|
||||
// Use SetWindowLongPtr if available (64-bit safe)
|
||||
user32.SetWindowLongPtr(hwnd, GWL_WNDPROC, proc);
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Registered 64-bit callback");
|
||||
} catch(UnsatisfiedLinkError e) {
|
||||
// Use SetWindowLong if SetWindowLongPtr isn't available
|
||||
user32.SetWindowLong(hwnd, GWL_WNDPROC, proc);
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Registered 32-bit callback");
|
||||
}
|
||||
// Handle events until the window is destroyed
|
||||
MSG msg = new MSG();
|
||||
while(user32.GetMessage(msg, null, 0, 0) > 0) {
|
||||
user32.TranslateMessage(msg);
|
||||
user32.DispatchMessage(msg);
|
||||
}
|
||||
} catch(UnsatisfiedLinkError e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class StartOnce extends Thread {
|
||||
|
||||
private final AtomicBoolean called = new AtomicBoolean(false);
|
||||
|
||||
private StartOnce(Runnable r) {
|
||||
super(r, "ShutdownManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// Ensure the thread is only started once
|
||||
if(!called.getAndSet(true)) super.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static interface User32 extends StdCallLibrary {
|
||||
|
||||
HWND CreateWindowEx(int styleEx, String className, String windowName,
|
||||
int style, int x, int y, int width, int height, HWND parent,
|
||||
HMENU menu, HINSTANCE instance, Pointer param);
|
||||
|
||||
LRESULT DefWindowProc(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
|
||||
LRESULT SetWindowLong(HWND hwnd, int index, WindowProc newProc);
|
||||
LRESULT SetWindowLongPtr(HWND hwnd, int index, WindowProc newProc);
|
||||
|
||||
int GetMessage(MSG msg, HWND hwnd, int filterMin, int filterMax);
|
||||
boolean TranslateMessage(MSG msg);
|
||||
LRESULT DispatchMessage(MSG msg);
|
||||
}
|
||||
|
||||
private static interface WindowProc extends StdCallCallback {
|
||||
|
||||
public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package net.sf.briar.plugins;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.lifecycle.ShutdownManager;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginConfig;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginConfig;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
|
||||
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
|
||||
import net.sf.briar.plugins.bluetooth.BluetoothPluginFactory;
|
||||
import net.sf.briar.plugins.file.RemovableDrivePluginFactory;
|
||||
import net.sf.briar.plugins.modem.ModemPluginFactory;
|
||||
import net.sf.briar.plugins.tcp.LanTcpPluginFactory;
|
||||
import net.sf.briar.plugins.tcp.WanTcpPluginFactory;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
public class JavaSePluginsModule extends AbstractModule {
|
||||
|
||||
public void configure() {}
|
||||
|
||||
@Provides
|
||||
SimplexPluginConfig getSimplexPluginConfig(
|
||||
@PluginExecutor Executor pluginExecutor) {
|
||||
SimplexPluginFactory removable =
|
||||
new RemovableDrivePluginFactory(pluginExecutor);
|
||||
final Collection<SimplexPluginFactory> factories =
|
||||
Arrays.asList(removable);
|
||||
return new SimplexPluginConfig() {
|
||||
public Collection<SimplexPluginFactory> getFactories() {
|
||||
return factories;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
DuplexPluginConfig getDuplexPluginConfig(
|
||||
@PluginExecutor Executor pluginExecutor,
|
||||
CryptoComponent crypto, ReliabilityLayerFactory reliabilityFactory,
|
||||
ShutdownManager shutdownManager) {
|
||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(
|
||||
pluginExecutor, crypto.getSecureRandom());
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(pluginExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(pluginExecutor);
|
||||
DuplexPluginFactory wan = new WanTcpPluginFactory(pluginExecutor,
|
||||
shutdownManager);
|
||||
final Collection<DuplexPluginFactory> factories =
|
||||
Arrays.asList(bluetooth, modem, lan, wan);
|
||||
return new DuplexPluginConfig() {
|
||||
public Collection<DuplexPluginFactory> getFactories() {
|
||||
return factories;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.util.LatchedReference;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
// Share an ID with the Android Bluetooth plugin
|
||||
static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
|
||||
+ "00a539fd260f08a13a0d8a900cde5e49"
|
||||
+ "1b4df2ffd42e40c408f2db7868f518aa");
|
||||
static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
private static final int UUID_BYTES = 16;
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final Clock clock;
|
||||
private final SecureRandom secureRandom;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxFrameLength;
|
||||
private final long maxLatency, pollingInterval;
|
||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile StreamConnectionNotifier socket = null;
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
BluetoothPlugin(Executor pluginExecutor, Clock clock,
|
||||
SecureRandom secureRandom, DuplexPluginCallback callback,
|
||||
int maxFrameLength, long maxLatency, long pollingInterval) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.clock = clock;
|
||||
this.secureRandom = secureRandom;
|
||||
this.callback = callback;
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
this.maxLatency = maxLatency;
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "BLUETOOTH_PLUGIN_NAME";
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return maxFrameLength;
|
||||
}
|
||||
|
||||
public long getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
public boolean start() throws IOException {
|
||||
// Initialise the Bluetooth stack
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch(UnsatisfiedLinkError e) {
|
||||
// On Linux the user may need to install libbluetooth-dev
|
||||
if(OsUtils.isLinux())
|
||||
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
|
||||
return false;
|
||||
}
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + localDevice.getBluetoothAddress());
|
||||
running = true;
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
bind();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
if(!running) return;
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", localDevice.getBluetoothAddress());
|
||||
callback.mergeLocalProperties(p);
|
||||
// Bind a server socket to accept connections from contacts
|
||||
String url = makeUrl("localhost", getUuid());
|
||||
StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if(!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
acceptContactConnections(ss);
|
||||
}
|
||||
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private String getUuid() {
|
||||
String uuid = callback.getLocalProperties().get("uuid");
|
||||
if(uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("uuid", uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private void tryToClose(StreamConnectionNotifier ss) {
|
||||
try {
|
||||
if(ss != null) ss.close();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
||||
while(true) {
|
||||
StreamConnection s;
|
||||
try {
|
||||
s = ss.acceptAndOpen();
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
callback.incomingConnectionCreated(wrapSocket(s));
|
||||
if(!running) return;
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
}
|
||||
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getPollingInterval() {
|
||||
return pollingInterval;
|
||||
}
|
||||
|
||||
public void poll(final Collection<ContactId> connected) {
|
||||
if(!running) return;
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
if(connected.contains(c)) continue;
|
||||
final String address = e.getValue().get("address");
|
||||
if(StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get("uuid");
|
||||
if(StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
if(!running) return;
|
||||
StreamConnection s = connect(makeUrl(address, uuid));
|
||||
if(s != null)
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private StreamConnection connect(String url) {
|
||||
try {
|
||||
return (StreamConnection) Connector.open(url);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if(!running) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if(p == null) return null;
|
||||
String address = p.get("address");
|
||||
if(StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get("uuid");
|
||||
if(StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if(s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout) {
|
||||
if(!running) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving invitation connections
|
||||
final StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if(!running) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
// Start the background threads
|
||||
LatchedReference<StreamConnection> socketLatch =
|
||||
new LatchedReference<StreamConnection>();
|
||||
new DiscoveryThread(socketLatch, uuid, timeout).start();
|
||||
new BluetoothListenerThread(socketLatch, ss).start();
|
||||
// Wait for an incoming or outgoing connection
|
||||
try {
|
||||
StreamConnection s = socketLatch.waitForReference(timeout);
|
||||
if(s != null) return new BluetoothTransportConnection(this, s);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener thread
|
||||
tryToClose(ss);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryThread extends Thread {
|
||||
|
||||
private final LatchedReference<StreamConnection> socketLatch;
|
||||
private final String uuid;
|
||||
private final long timeout;
|
||||
|
||||
private DiscoveryThread(LatchedReference<StreamConnection> socketLatch,
|
||||
String uuid, long timeout) {
|
||||
this.socketLatch = socketLatch;
|
||||
this.uuid = uuid;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
while(now < end && running && !socketLatch.isSet()) {
|
||||
if(!discoverySemaphore.tryAcquire()) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Another device discovery is in progress");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
InvitationListener listener =
|
||||
new InvitationListener(discoveryAgent, uuid);
|
||||
discoveryAgent.startInquiry(GIAC, listener);
|
||||
String url = listener.waitForUrl();
|
||||
if(url == null) continue;
|
||||
StreamConnection s = connect(url);
|
||||
if(s == null) continue;
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Outgoing connection");
|
||||
if(!socketLatch.set(s)) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Closing redundant connection");
|
||||
tryToClose(s);
|
||||
}
|
||||
return;
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while waiting for URL");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} finally {
|
||||
discoverySemaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(StreamConnection s) {
|
||||
try {
|
||||
s.close();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BluetoothListenerThread extends Thread {
|
||||
|
||||
private final LatchedReference<StreamConnection> socketLatch;
|
||||
private final StreamConnectionNotifier serverSocket;
|
||||
|
||||
private BluetoothListenerThread(
|
||||
LatchedReference<StreamConnection> socketLatch,
|
||||
StreamConnectionNotifier serverSocket) {
|
||||
this.socketLatch = socketLatch;
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Listening for invitation connections");
|
||||
// Listen until a connection is received or the socket is closed
|
||||
try {
|
||||
StreamConnection s = serverSocket.acceptAndOpen();
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
|
||||
if(!socketLatch.set(s)) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Closing redundant connection");
|
||||
s.close();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.clock.SystemClock;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
|
||||
public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_FRAME_LENGTH = 1024;
|
||||
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
|
||||
private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Clock clock;
|
||||
|
||||
public BluetoothPluginFactory(Executor pluginExecutor,
|
||||
SecureRandom secureRandom) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
clock = new SystemClock();
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return BluetoothPlugin.ID;
|
||||
}
|
||||
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
return new BluetoothPlugin(pluginExecutor, clock, secureRandom,
|
||||
callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.microedition.io.StreamConnection;
|
||||
|
||||
import net.sf.briar.api.plugins.Plugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
class BluetoothTransportConnection implements DuplexTransportConnection {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final StreamConnection stream;
|
||||
|
||||
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
this.plugin = plugin;
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return plugin.getMaxFrameLength();
|
||||
}
|
||||
|
||||
public long getMaxLatency() {
|
||||
return plugin.getMaxLatency();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return stream.openInputStream();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return stream.openOutputStream();
|
||||
}
|
||||
|
||||
public boolean shouldFlush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dispose(boolean exception, boolean recognised)
|
||||
throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
class InvitationListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final String uuid;
|
||||
|
||||
private volatile String url = null;
|
||||
|
||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
String waitForUrl() throws InterruptedException {
|
||||
finished.await();
|
||||
return url;
|
||||
}
|
||||
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
searches.incrementAndGet();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for(ServiceRecord record : services) {
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if(serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
Collection<String> uuids = new TreeSet<String>();
|
||||
findNestedClassIds(record.getAttributeValue(0x1), uuids);
|
||||
for(String u : uuids) {
|
||||
if(uuid.equalsIgnoreCase(u)) {
|
||||
// The UUID matches - store the URL
|
||||
url = serviceUrl;
|
||||
finished.countDown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
// UUIDs are sometimes buried in nested data elements
|
||||
private void findNestedClassIds(Object o, Collection<String> ids) {
|
||||
o = getDataElementValue(o);
|
||||
if(o instanceof Enumeration<?>) {
|
||||
for(Object o1 : Collections.list((Enumeration<?>) o))
|
||||
findNestedClassIds(o1, ids);
|
||||
} else if(o instanceof UUID) {
|
||||
ids.add(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDataElementValue(Object o) {
|
||||
if(o instanceof DataElement) {
|
||||
// Bluecove throws an exception if the type is unknown
|
||||
try {
|
||||
return ((DataElement) o).getValue();
|
||||
} catch(ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
class LinuxRemovableDriveFinder extends UnixRemovableDriveFinder {
|
||||
|
||||
@Override
|
||||
protected String getMountCommand() {
|
||||
return "/bin/mount";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parseMountPoint(String line) {
|
||||
// The format is "/dev/foo on /bar/baz type bam (opt1,opt2)"
|
||||
String pattern = "^/dev/[^ ]+ on (.*) type [^ ]+ \\([^)]+\\)$";
|
||||
String path = line.replaceFirst(pattern, "$1");
|
||||
return path.equals(line) ? null : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRemovableDriveMountPoint(String path) {
|
||||
return path.startsWith("/mnt/") || path.startsWith("/media/");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
|
||||
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] { "/mnt", "/media" };
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
class MacRemovableDriveFinder extends UnixRemovableDriveFinder {
|
||||
|
||||
@Override
|
||||
protected String getMountCommand() {
|
||||
return "/sbin/mount";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parseMountPoint(String line) {
|
||||
// The format is "/dev/foo on /bar/baz (opt1, opt2)"
|
||||
String pattern = "^/dev/[^ ]+ on (.*) \\([^)]+\\)$";
|
||||
String path = line.replaceFirst(pattern, "$1");
|
||||
return path.equals(line) ? null : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRemovableDriveMountPoint(String path) {
|
||||
return path.startsWith("/Volumes/");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
|
||||
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] { "/Volumes" };
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final RemovableDriveFinder finder;
|
||||
private final long pollingInterval;
|
||||
private final Object pollingLock = new Object();
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile Callback callback = null;
|
||||
|
||||
public PollingRemovableDriveMonitor(Executor pluginExecutor,
|
||||
RemovableDriveFinder finder, long pollingInterval) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.finder = finder;
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
public void start(Callback callback) throws IOException {
|
||||
this.callback = callback;
|
||||
running = true;
|
||||
pluginExecutor.execute(this);
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
running = false;
|
||||
synchronized(pollingLock) {
|
||||
pollingLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Collection<File> drives = finder.findRemovableDrives();
|
||||
while(running) {
|
||||
synchronized(pollingLock) {
|
||||
pollingLock.wait(pollingInterval);
|
||||
}
|
||||
if(!running) return;
|
||||
Collection<File> newDrives = finder.findRemovableDrives();
|
||||
for(File f : newDrives) {
|
||||
if(!drives.contains(f)) callback.driveInserted(f);
|
||||
}
|
||||
drives = newDrives;
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while waiting to poll");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch(IOException e) {
|
||||
callback.exceptionThrown(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
interface RemovableDriveFinder {
|
||||
|
||||
Collection<File> findRemovableDrives() throws IOException;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
interface RemovableDriveMonitor {
|
||||
|
||||
void start(Callback c) throws IOException;
|
||||
|
||||
void stop() throws IOException;
|
||||
|
||||
interface Callback {
|
||||
|
||||
void driveInserted(File root);
|
||||
|
||||
void exceptionThrown(IOException e);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
class RemovableDrivePlugin extends FilePlugin
|
||||
implements RemovableDriveMonitor.Callback {
|
||||
|
||||
static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("7c81bf5c9b1cd557685548c85f976bbd"
|
||||
+ "e633d2418ea2e230e5710fb43c6f8cc0"
|
||||
+ "68abca3a9d0edb13bcea13b851725c5d");
|
||||
static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(RemovableDrivePlugin.class.getName());
|
||||
|
||||
private final RemovableDriveFinder finder;
|
||||
private final RemovableDriveMonitor monitor;
|
||||
|
||||
RemovableDrivePlugin(Executor pluginExecutor,
|
||||
SimplexPluginCallback callback, RemovableDriveFinder finder,
|
||||
RemovableDriveMonitor monitor, int maxFrameLength,
|
||||
long maxLatency) {
|
||||
super(pluginExecutor, callback, maxFrameLength, maxLatency);
|
||||
this.finder = finder;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "REMOVABLE_DRIVE_PLUGIN_NAME";
|
||||
}
|
||||
|
||||
public boolean start() throws IOException {
|
||||
running = true;
|
||||
monitor.start(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
running = false;
|
||||
monitor.stop();
|
||||
}
|
||||
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getPollingInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File chooseOutputDirectory() {
|
||||
try {
|
||||
List<File> drives =
|
||||
new ArrayList<File>(finder.findRemovableDrives());
|
||||
if(drives.isEmpty()) return null;
|
||||
String[] paths = new String[drives.size()];
|
||||
for(int i = 0; i < paths.length; i++) {
|
||||
paths[i] = drives.get(i).getPath();
|
||||
}
|
||||
int i = callback.showChoice(paths, "REMOVABLE_DRIVE_CHOOSE_DRIVE");
|
||||
if(i == -1) return null;
|
||||
return drives.get(i);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readerFinished(File f) {
|
||||
callback.showMessage("REMOVABLE_DRIVE_READ_FINISHED");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writerFinished(File f) {
|
||||
callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<File> findFilesByName(String filename) {
|
||||
List<File> matches = new ArrayList<File>();
|
||||
try {
|
||||
for(File drive : finder.findRemovableDrives()) {
|
||||
File[] files = drive.listFiles();
|
||||
if(files != null) {
|
||||
for(File f : files) {
|
||||
if(f.isFile() && filename.equals(f.getName()))
|
||||
matches.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
return Collections.unmodifiableList(matches);
|
||||
}
|
||||
|
||||
public void driveInserted(File root) {
|
||||
File[] files = root.listFiles();
|
||||
if(files != null) {
|
||||
for(File f : files) if(f.isFile()) createReaderFromFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
public void exceptionThrown(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
|
||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||
|
||||
// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
|
||||
private static final long MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
|
||||
private static final long POLLING_INTERVAL = 10 * 1000; // 10 seconds
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
|
||||
public RemovableDrivePluginFactory(Executor pluginExecutor) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return RemovableDrivePlugin.ID;
|
||||
}
|
||||
|
||||
public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
|
||||
RemovableDriveFinder finder;
|
||||
RemovableDriveMonitor monitor;
|
||||
if(OsUtils.isLinux()) {
|
||||
finder = new LinuxRemovableDriveFinder();
|
||||
monitor = new LinuxRemovableDriveMonitor();
|
||||
} else if(OsUtils.isMacLeopardOrNewer()) {
|
||||
finder = new MacRemovableDriveFinder();
|
||||
monitor = new MacRemovableDriveMonitor();
|
||||
} else if(OsUtils.isMac()) {
|
||||
// JNotify requires OS X 10.5 or newer, so we have to poll
|
||||
finder = new MacRemovableDriveFinder();
|
||||
monitor = new PollingRemovableDriveMonitor(pluginExecutor, finder,
|
||||
POLLING_INTERVAL);
|
||||
} else if(OsUtils.isWindows()) {
|
||||
finder = new WindowsRemovableDriveFinder();
|
||||
monitor = new PollingRemovableDriveMonitor(pluginExecutor, finder,
|
||||
POLLING_INTERVAL);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return new RemovableDrivePlugin(pluginExecutor, callback, finder,
|
||||
monitor, MAX_FRAME_LENGTH, MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
|
||||
|
||||
protected abstract String getMountCommand();
|
||||
protected abstract String parseMountPoint(String line);
|
||||
protected abstract boolean isRemovableDriveMountPoint(String path);
|
||||
|
||||
public List<File> findRemovableDrives() throws IOException {
|
||||
List<File> drives = new ArrayList<File>();
|
||||
Process p = new ProcessBuilder(getMountCommand()).start();
|
||||
Scanner s = new Scanner(p.getInputStream(), "UTF-8");
|
||||
try {
|
||||
while(s.hasNextLine()) {
|
||||
String line = s.nextLine();
|
||||
String[] tokens = line.split(" ");
|
||||
if(tokens.length < 3) continue;
|
||||
// The general format is "/dev/foo on /bar/baz ..."
|
||||
if(tokens[0].startsWith("/dev/") && tokens[1].equals("on")) {
|
||||
// The path may contain spaces so we can't use tokens[2]
|
||||
String path = parseMountPoint(line);
|
||||
if(isRemovableDriveMountPoint(path)) {
|
||||
File f = new File(path);
|
||||
if(f.exists() && f.isDirectory()) drives.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
s.close();
|
||||
}
|
||||
return Collections.unmodifiableList(drives);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.contentobjects.jnotify.JNotify;
|
||||
import net.contentobjects.jnotify.JNotifyListener;
|
||||
|
||||
abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
|
||||
JNotifyListener {
|
||||
|
||||
// Locking: this
|
||||
private final List<Integer> watches = new ArrayList<Integer>();
|
||||
|
||||
private boolean started = false; // Locking: this
|
||||
private Callback callback = null; // Locking: this
|
||||
|
||||
protected abstract String[] getPathsToWatch();
|
||||
|
||||
public void start(Callback callback) throws IOException {
|
||||
List<Integer> watches = new ArrayList<Integer>();
|
||||
int mask = JNotify.FILE_CREATED;
|
||||
for(String path : getPathsToWatch()) {
|
||||
if(new File(path).exists())
|
||||
watches.add(JNotify.addWatch(path, mask, false, this));
|
||||
}
|
||||
synchronized(this) {
|
||||
assert !started;
|
||||
assert this.callback == null;
|
||||
started = true;
|
||||
this.callback = callback;
|
||||
this.watches.addAll(watches);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
List<Integer> watches;
|
||||
synchronized(this) {
|
||||
assert started;
|
||||
assert callback != null;
|
||||
started = false;
|
||||
callback = null;
|
||||
watches = new ArrayList<Integer>(this.watches);
|
||||
this.watches.clear();
|
||||
}
|
||||
for(Integer w : watches) JNotify.removeWatch(w);
|
||||
}
|
||||
|
||||
public void fileCreated(int wd, String rootPath, String name) {
|
||||
Callback callback;
|
||||
synchronized(this) {
|
||||
callback = this.callback;
|
||||
}
|
||||
if(callback != null)
|
||||
callback.driveInserted(new File(rootPath + "/" + name));
|
||||
}
|
||||
|
||||
public void fileDeleted(int wd, String rootPath, String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void fileModified(int wd, String rootPath, String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void fileRenamed(int wd, String rootPath, String oldName,
|
||||
String newName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
|
||||
class WindowsRemovableDriveFinder implements RemovableDriveFinder {
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
|
||||
private static final int DRIVE_REMOVABLE = 2;
|
||||
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
File[] roots = File.listRoots();
|
||||
if(roots == null) throw new IOException();
|
||||
List<File> drives = new ArrayList<File>();
|
||||
for(File root : roots) {
|
||||
try {
|
||||
int type = Kernel32.INSTANCE.GetDriveType(root.getPath());
|
||||
if(type == DRIVE_REMOVABLE) drives.add(root);
|
||||
} catch(RuntimeException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(drives);
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class CountryCodes {
|
||||
|
||||
private static final Country[] COUNTRIES = {
|
||||
new Country("AD", "Andorra", "376", "00", ""),
|
||||
new Country("AE", "United Arab Emirates", "971", "00", "0"),
|
||||
new Country("AF", "Afghanistan", "93", "00", "0"),
|
||||
new Country("AG", "Antigua and Barbuda", "1", "011", "1"),
|
||||
new Country("AI", "Anguilla", "1", "011", "1"),
|
||||
new Country("AL", "Albania", "355", "00", "0"),
|
||||
new Country("AM", "Armenia", "374", "00", "8"),
|
||||
new Country("AN", "Netherlands Antilles", "599", "00", "0"),
|
||||
new Country("AO", "Angola", "244", "00", "0"),
|
||||
new Country("AQ", "Antarctica", "672", "", ""),
|
||||
new Country("AR", "Argentina", "54", "00", "0"),
|
||||
new Country("AS", "American Samoa", "1", "011", "1"),
|
||||
new Country("AT", "Austria", "43", "00", "0"),
|
||||
new Country("AU", "Australia", "61", "0011", "0"),
|
||||
new Country("AW", "Aruba", "297", "00", ""),
|
||||
new Country("AZ", "Azerbaijan", "994", "00", "8"),
|
||||
new Country("BA", "Bosnia and Herzegovina", "387", "00", "0"),
|
||||
new Country("BB", "Barbados", "1", "011", "1"),
|
||||
new Country("BD", "Bangladesh", "880", "00", "0"),
|
||||
new Country("BE", "Belgium", "32", "00", "0"),
|
||||
new Country("BF", "Burkina Faso", "226", "00", ""),
|
||||
new Country("BG", "Bulgaria", "359", "00", "0"),
|
||||
new Country("BH", "Bahrain", "973", "00", ""),
|
||||
new Country("BI", "Burundi", "257", "00", ""),
|
||||
new Country("BJ", "Benin", "229", "00", ""),
|
||||
new Country("BM", "Bermuda", "1", "011", "1"),
|
||||
new Country("BN", "Brunei Darussalam", "673", "00", "0"),
|
||||
new Country("BO", "Bolivia", "591", "00", "0"),
|
||||
new Country("BR", "Brazil", "55", "00", "0"),
|
||||
new Country("BS", "Bahamas", "1", "011", "1"),
|
||||
new Country("BT", "Bhutan", "975", "00", ""),
|
||||
new Country("BV", "Bouvet Island (Norway)", "47", "00", ""),
|
||||
new Country("BW", "Botswana", "267", "00", ""),
|
||||
new Country("BY", "Belarus", "375", "8**10", "8"),
|
||||
new Country("BZ", "Belize", "501", "00", "0"),
|
||||
new Country("CA", "Canada", "1", "011", "1"),
|
||||
new Country("CC", "Cocos (Keeling) Islands", "61", "0011", "0"),
|
||||
new Country("CD", "Congo (Republic)", "243", "00", ""),
|
||||
new Country("CF", "Central African Republic", "236", "00", ""),
|
||||
new Country("CG", "Congo (Democratic Republic)", "242", "00", "0"),
|
||||
new Country("CH", "Switzerland", "41", "00", "0"),
|
||||
new Country("CI", "Cote D'Ivoire", "225", "00", "0"),
|
||||
new Country("CK", "Cook Islands", "682", "00", "00"),
|
||||
new Country("CL", "Chile", "56", "00", "0"),
|
||||
new Country("CM", "Cameroon", "237", "00", ""),
|
||||
new Country("CN", "China", "86", "00", "0"),
|
||||
new Country("CO", "Colombia", "57", "009", "09"),
|
||||
new Country("CR", "Costa Rica", "506", "00", ""),
|
||||
new Country("CU", "Cuba", "53", "119", "0"),
|
||||
new Country("CV", "Cape Verde Islands", "238", "0", ""),
|
||||
new Country("CX", "Christmas Island", "61", "0011", "0"),
|
||||
new Country("CY", "Cyprus", "357", "00", ""),
|
||||
new Country("CZ", "Czech Republic", "420", "00", ""),
|
||||
new Country("DE", "Germany", "49", "00", "0"),
|
||||
new Country("DJ", "Djibouti", "253", "00", ""),
|
||||
new Country("DK", "Denmark", "45", "00", ""),
|
||||
new Country("DM", "Dominica", "1", "011", "1"),
|
||||
new Country("DO", "Dominican Republic", "1", "011", "1"),
|
||||
new Country("DZ", "Algeria", "213", "00", "7"),
|
||||
new Country("EC", "Ecuador", "593", "00", "0"),
|
||||
new Country("EE", "Estonia", "372", "00", ""),
|
||||
new Country("EG", "Egypt", "20", "00", "0"),
|
||||
new Country("EH", "Western Sahara", "212", "00", "0"),
|
||||
new Country("ER", "Eritrea", "291", "00", "0"),
|
||||
new Country("ES", "Spain", "34", "00", ""),
|
||||
new Country("ET", "Ethiopia", "251", "00", "0"),
|
||||
new Country("FI", "Finland", "358", "00", "0"),
|
||||
new Country("FJ", "Fiji", "679", "00", ""),
|
||||
new Country("FK", "Falkland Islands (Malvinas)", "500", "00", ""),
|
||||
new Country("FM", "Micronesia", "691", "011", "1"),
|
||||
new Country("FO", "Faroe Islands", "298", "00", ""),
|
||||
new Country("FR", "France", "33", "00", ""),
|
||||
new Country("GA", "Gabonese Republic", "241", "00", ""),
|
||||
new Country("GB", "United Kingdom", "44", "00", "0"),
|
||||
new Country("GD", "Grenada", "1", "011", "4"),
|
||||
new Country("GE", "Georgia", "995", "8**10", "8"),
|
||||
new Country("GF", "French Guiana", "594", "00", ""),
|
||||
new Country("GH", "Ghana", "233", "00", ""),
|
||||
new Country("GI", "Gibraltar", "350", "00", ""),
|
||||
new Country("GL", "Greenland", "299", "00", ""),
|
||||
new Country("GM", "Gambia", "220", "00", ""),
|
||||
new Country("GN", "Guinea", "224", "00", "0"),
|
||||
new Country("GP", "Guadeloupe", "590", "00", ""),
|
||||
new Country("GQ", "Equatorial Guinea", "240", "00", ""),
|
||||
new Country("GR", "Greece", "30", "00", ""),
|
||||
new Country("GS", "South Georgia and the South Sandwich Islands", "995", "8**10", "8"),
|
||||
new Country("GT", "Guatemala", "502", "00", ""),
|
||||
new Country("GU", "Guam", "1", "011", "1"),
|
||||
new Country("GW", "Guinea-Bissau", "245", "00", ""),
|
||||
new Country("GY", "Guyana", "592", "001", "0"),
|
||||
new Country("HK", "Hong Kong", "852", "001", ""),
|
||||
new Country("HM", "Heard Island and McDonald Islands", "692", "00", "0"),
|
||||
new Country("HN", "Honduras", "504", "00", "0"),
|
||||
new Country("HR", "Croatia", "385", "00", "0"),
|
||||
new Country("HT", "Haiti", "509", "00", "0"),
|
||||
new Country("HU", "Hungary", "36", "00", "06"),
|
||||
new Country("ID", "Indonesia", "62", "001", "0"),
|
||||
new Country("IE", "Ireland", "353", "00", "0"),
|
||||
new Country("IL", "Israel", "972", "00", "0"),
|
||||
new Country("IN", "India", "91", "00", "0"),
|
||||
new Country("IO", "British Indian Ocean Territory", "246", "00", ""),
|
||||
new Country("IQ", "Iraq", "964", "00", "0"),
|
||||
new Country("IR", "Iran", "98", "00", "0"),
|
||||
new Country("IS", "Iceland", "354", "00", "0"),
|
||||
new Country("IT", "Italy", "39", "00", ""),
|
||||
new Country("JM", "Jamaica", "1", "011", "1"),
|
||||
new Country("JO", "Jordan", "962", "00", "0"),
|
||||
new Country("JP", "Japan", "81", "001", "0"),
|
||||
new Country("KE", "Kenya", "254", "000", "0"),
|
||||
new Country("KG", "Kyrgyzstan", "996", "00", "0"),
|
||||
new Country("KH", "Cambodia", "855", "001", "0"),
|
||||
new Country("KI", "Kiribati", "686", "00", "0"),
|
||||
new Country("KM", "Comoros", "269", "00", ""),
|
||||
new Country("KN", "Saint Kitts and Nevis", "1", "011", "1"),
|
||||
new Country("KP", "Korea (North)", "850", "00", "0"),
|
||||
new Country("KR", "Korea (South)", "82", "001", "0"),
|
||||
new Country("KW", "Kuwait", "965", "00", "0"),
|
||||
new Country("KY", "Cayman Islands", "1", "011", "1"),
|
||||
new Country("KZ", "Kazakhstan", "7", "8**10", "8"),
|
||||
new Country("LA", "Laos", "856", "00", "0"),
|
||||
new Country("LB", "Lebanon", "961", "00", "0"),
|
||||
new Country("LC", "Saint Lucia", "1", "011", "1"),
|
||||
new Country("LI", "Liechtenstein", "423", "00", ""),
|
||||
new Country("LK", "Sri Lanka", "94", "00", "0"),
|
||||
new Country("LR", "Liberia", "231", "00", "22"),
|
||||
new Country("LS", "Lesotho", "266", "00", "0"),
|
||||
new Country("LT", "Lithuania", "370", "00", "8"),
|
||||
new Country("LU", "Luxembourg", "352", "00", ""),
|
||||
new Country("LV", "Latvia", "371", "00", "8"),
|
||||
new Country("LY", "Libya", "218", "00", "0"),
|
||||
new Country("MA", "Morocco", "212", "00", ""),
|
||||
new Country("MC", "Monaco", "377", "00", "0"),
|
||||
new Country("MD", "Moldova", "373", "00", "0"),
|
||||
new Country("ME", "Montenegro", "382", "99", "0"),
|
||||
new Country("MG", "Madagascar", "261", "00", "0"),
|
||||
new Country("MH", "Marshall Islands", "692", "011", "1"),
|
||||
new Country("MK", "Macedonia", "389", "00", "0"),
|
||||
new Country("ML", "Mali", "223", "00", "0"),
|
||||
new Country("MM", "Myanmar", "95", "00", ""),
|
||||
new Country("MN", "Mongolia", "976", "001", "0"),
|
||||
new Country("MO", "Macao", "853", "00", "0"),
|
||||
new Country("MP", "Northern Mariana Islands", "1", "011", "1"),
|
||||
new Country("MQ", "Martinique", "596", "00", "0"),
|
||||
new Country("MR", "Mauritania", "222", "00", "0"),
|
||||
new Country("MS", "Montserrat", "1", "011", "1"),
|
||||
new Country("MT", "Malta", "356", "00", "21"),
|
||||
new Country("MU", "Mauritius", "230", "00", "0"),
|
||||
new Country("MV", "Maldives", "960", "00", "0"),
|
||||
new Country("MW", "Malawi", "265", "00", ""),
|
||||
new Country("MX", "Mexico", "52", "00", "01"),
|
||||
new Country("MY", "Malaysia", "60", "00", "0"),
|
||||
new Country("MZ", "Mozambique", "258", "00", "0"),
|
||||
new Country("NA", "Namibia", "264", "00", "0"),
|
||||
new Country("NC", "New Caledonia", "687", "00", "0"),
|
||||
new Country("NE", "Niger", "227", "00", "0"),
|
||||
new Country("NF", "Norfolk Island", "672", "00", ""),
|
||||
new Country("NG", "Nigeria", "234", "009", "0"),
|
||||
new Country("NI", "Nicaragua", "505", "00", "0"),
|
||||
new Country("NL", "Netherlands", "31", "00", "0"),
|
||||
new Country("NO", "Norway", "47", "00", ""),
|
||||
new Country("NP", "Nepal", "977", "00", "0"),
|
||||
new Country("NR", "Nauru", "674", "00", "0"),
|
||||
new Country("NU", "Niue", "683", "00", "0"),
|
||||
new Country("NZ", "New Zealand", "64", "00", "0"),
|
||||
new Country("OM", "Oman", "968", "00", "0"),
|
||||
new Country("PA", "Panama", "507", "00", "0"),
|
||||
new Country("PE", "Peru", "51", "00", "0"),
|
||||
new Country("PF", "French Polynesia", "689", "00", ""),
|
||||
new Country("PG", "Papua New Guinea", "675", "05", ""),
|
||||
new Country("PH", "Philippines", "63", "00", "0"),
|
||||
new Country("PK", "Pakistan", "92", "00", "0"),
|
||||
new Country("PL", "Poland", "48", "00", "0"),
|
||||
new Country("PM", "Saint Pierre and Miquelon", "508", "00", "0"),
|
||||
new Country("PN", "Pitcairn", "872", "", ""),
|
||||
new Country("PR", "Puerto Rico", "1", "011", "1"),
|
||||
new Country("PS", "Palestine", "970", "00", "0"),
|
||||
new Country("PT", "Portugal", "351", "00", ""),
|
||||
new Country("PW", "Palau", "680", "011", ""),
|
||||
new Country("PY", "Paraguay", "595", "002", "0"),
|
||||
new Country("QA", "Qatar", "974", "00", "0"),
|
||||
new Country("RE", "Reunion", "262", "00", "0"),
|
||||
new Country("RO", "Romania", "40", "00", "0"),
|
||||
new Country("RS", "Serbia", "381", "99", "0"),
|
||||
new Country("RU", "Russia", "7", "8**10", "8"),
|
||||
new Country("RW", "Rwanda", "250", "00", "0"),
|
||||
new Country("SA", "Saudi Arabia", "966", "00", "0"),
|
||||
new Country("SB", "Solomon Islands", "677", "00", ""),
|
||||
new Country("SC", "Seychelles", "248", "00", "0"),
|
||||
new Country("SD", "Sudan", "249", "00", "0"),
|
||||
new Country("SE", "Sweden", "46", "00", "0"),
|
||||
new Country("SG", "Singapore", "65", "001", ""),
|
||||
new Country("SH", "Saint Helena", "290", "00", ""),
|
||||
new Country("SI", "Slovenia", "386", "00", "0"),
|
||||
new Country("SJ", "Svalbard and Jan Mayen", "378", "00", "0"),
|
||||
new Country("SK", "Slovakia", "421", "00", "0"),
|
||||
new Country("SL", "Sierra Leone", "232", "00", "0"),
|
||||
new Country("SM", "San Marino", "378", "00", "0"),
|
||||
new Country("SN", "Senegal", "221", "00", "0"),
|
||||
new Country("SO", "Somalia", "252", "00", ""),
|
||||
new Country("SR", "Suriname", "597", "00", ""),
|
||||
new Country("ST", "Sao Tome and Principe", "239", "00", "0"),
|
||||
new Country("SV", "El Salvador", "503", "00", ""),
|
||||
new Country("SY", "Syria", "963", "00", "0"),
|
||||
new Country("SZ", "Swaziland", "268", "00", ""),
|
||||
new Country("TC", "Turks and Caicos Islands", "1", "011", "1"),
|
||||
new Country("TD", "Chad", "235", "15", ""),
|
||||
new Country("TF", "French Southern Territories", "596", "00", "0"),
|
||||
new Country("TG", "Togo", "228", "00", ""),
|
||||
new Country("TH", "Thailand", "66", "001", "0"),
|
||||
new Country("TJ", "Tajikistan", "992", "8**10", "8"),
|
||||
new Country("TK", "Tokelau", "690", "00", ""),
|
||||
new Country("TL", "Timor-Leste", "670", "00", ""),
|
||||
new Country("TM", "Turkmenistan", "993", "8**10", "8"),
|
||||
new Country("TN", "Tunisia", "216", "00", "0"),
|
||||
new Country("TO", "Tonga Islands", "676", "00", ""),
|
||||
new Country("TR", "Turkey", "90", "00", "0"),
|
||||
new Country("TT", "Trinidad and Tobago", "1", "011", "1"),
|
||||
new Country("TV", "Tuvalu", "688", "00", ""),
|
||||
new Country("TW", "Taiwan", "886", "002", ""),
|
||||
new Country("TZ", "Tanzania", "255", "000", "0"),
|
||||
new Country("UA", "Ukraine", "380", "8**10", "8"),
|
||||
new Country("UG", "Uganda", "256", "000", "0"),
|
||||
new Country("US", "United States", "1", "011", "1"),
|
||||
new Country("UY", "Uruguay", "598", "00", "0"),
|
||||
new Country("UZ", "Uzbekistan", "998", "8**10", "8"),
|
||||
new Country("VA", "Holy See (Vatican City State)", "379", "00", ""),
|
||||
new Country("VC", "Saint Vincent and the Grenadines", "1", "011", "1"),
|
||||
new Country("VE", "Venezuela", "58", "00", "0"),
|
||||
new Country("VG", "Virgin Islands (British)", "1", "011", "1"),
|
||||
new Country("VI", "Virgin Islands (U.S.)", "1", "011", "1"),
|
||||
new Country("VN", "Viet Nam", "84", "00", "0"),
|
||||
new Country("VU", "Vanuatu", "678", "00", ""),
|
||||
new Country("WF", "Wallis and Futuna Islands", "681", "19", ""),
|
||||
new Country("WS", "Samoa (Western)", "685", "0", "0"),
|
||||
new Country("YE", "Yemen", "967", "00", "0"),
|
||||
new Country("YT", "Mayotte", "269", "00", ""),
|
||||
new Country("ZA", "South Africa", "27", "09", "0"),
|
||||
new Country("ZM", "Zambia", "260", "00", "0"),
|
||||
new Country("ZW", "Zimbabwe", "263", "110", "0")
|
||||
};
|
||||
|
||||
private static final Map<String, Country> COUNTRY_MAP =
|
||||
new HashMap<String, Country>();
|
||||
|
||||
static {
|
||||
for(Country c : COUNTRIES) COUNTRY_MAP.put(c.iso3166, c);
|
||||
}
|
||||
|
||||
static String translate(String number, String callerIso, String calleeIso) {
|
||||
Country from = COUNTRY_MAP.get(callerIso);
|
||||
Country to = COUNTRY_MAP.get(calleeIso);
|
||||
if(from == null || to == null) return null;
|
||||
// Strip any prefixes and country codes from the number
|
||||
String plusCountryCode = "+" + to.countryCode;
|
||||
String iddCountryCode = to.idd + to.countryCode;
|
||||
if(number.startsWith(plusCountryCode))
|
||||
number = number.substring(plusCountryCode.length());
|
||||
else if(number.startsWith(iddCountryCode))
|
||||
number = number.substring(iddCountryCode.length());
|
||||
else if(number.startsWith(to.ndd))
|
||||
number = number.substring(to.ndd.length());
|
||||
if(from == to) return from.ndd + number; // National
|
||||
return from.idd + to.countryCode + number; // International
|
||||
}
|
||||
|
||||
private static class Country {
|
||||
|
||||
private final String iso3166, countryCode, idd, ndd;
|
||||
|
||||
private Country(String iso3166, String englishName, String countryCode,
|
||||
String idd, String ndd) {
|
||||
this.iso3166 = iso3166;
|
||||
this.countryCode = countryCode;
|
||||
this.idd = idd;
|
||||
this.ndd = ndd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A modem that can be used for multiple sequential incoming and outgoing
|
||||
* calls. If an exception is thrown, a new modem instance must be created.
|
||||
*/
|
||||
interface Modem {
|
||||
|
||||
/**
|
||||
* Call this method after creating the modem and before making any calls.
|
||||
* If this method returns false the modem cannot be used.
|
||||
*/
|
||||
boolean start() throws IOException;
|
||||
|
||||
/**
|
||||
* Call this method when the modem is no longer needed. If a call is in
|
||||
* progress it will be terminated.
|
||||
*/
|
||||
void stop() throws IOException;
|
||||
|
||||
/**
|
||||
* Initiates an outgoing call and returns true if the call connects. If the
|
||||
* call does not connect the modem is hung up.
|
||||
*/
|
||||
boolean dial(String number) throws IOException;
|
||||
|
||||
/** Returns a stream for reading from the currently connected call. */
|
||||
InputStream getInputStream() throws IOException;
|
||||
|
||||
/** Returns a stream for writing to the currently connected call. */
|
||||
OutputStream getOutputStream() throws IOException;
|
||||
|
||||
/** Hangs up the modem, ending the currently connected call. */
|
||||
void hangUp() throws IOException;
|
||||
|
||||
interface Callback {
|
||||
|
||||
/** Called when an incoming call connects. */
|
||||
void incomingCallConnected();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
interface ModemFactory {
|
||||
|
||||
Modem createModem(Modem.Callback callback, String portName);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.clock.SystemClock;
|
||||
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
|
||||
|
||||
class ModemFactoryImpl implements ModemFactory {
|
||||
|
||||
private final Executor executor;
|
||||
private final ReliabilityLayerFactory reliabilityFactory;
|
||||
private final Clock clock;
|
||||
|
||||
ModemFactoryImpl(Executor executor,
|
||||
ReliabilityLayerFactory reliabilityFactory) {
|
||||
this.executor = executor;
|
||||
this.reliabilityFactory = reliabilityFactory;
|
||||
clock = new SystemClock();
|
||||
}
|
||||
|
||||
public Modem createModem(Modem.Callback callback, String portName) {
|
||||
return new ModemImpl(executor, reliabilityFactory, clock, callback,
|
||||
new SerialPortImpl(portName));
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static jssc.SerialPort.PURGE_RXCLEAR;
|
||||
import static jssc.SerialPort.PURGE_TXCLEAR;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jssc.SerialPortEvent;
|
||||
import jssc.SerialPortEventListener;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.reliability.ReliabilityLayer;
|
||||
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
|
||||
import net.sf.briar.api.reliability.WriteHandler;
|
||||
|
||||
class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ModemImpl.class.getName());
|
||||
private static final int MAX_LINE_LENGTH = 256;
|
||||
private static final int[] BAUD_RATES = {
|
||||
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200
|
||||
};
|
||||
private static final int OK_TIMEOUT = 5 * 1000; // Milliseconds
|
||||
private static final int CONNECT_TIMEOUT = 2 * 60 * 1000; // Milliseconds
|
||||
private static final int ESCAPE_SEQUENCE_GUARD_TIME = 1000; // Milliseconds
|
||||
|
||||
private final Executor executor;
|
||||
private final ReliabilityLayerFactory reliabilityFactory;
|
||||
private final Clock clock;
|
||||
private final Callback callback;
|
||||
private final SerialPort port;
|
||||
private final Semaphore stateChange;
|
||||
private final byte[] line;
|
||||
|
||||
private int lineLen = 0;
|
||||
|
||||
private ReliabilityLayer reliability = null; // Locking: this
|
||||
private boolean initialised = false, connected = false; // Locking: this
|
||||
|
||||
ModemImpl(Executor executor, ReliabilityLayerFactory reliabilityFactory,
|
||||
Clock clock, Callback callback, SerialPort port) {
|
||||
this.executor = executor;
|
||||
this.reliabilityFactory = reliabilityFactory;
|
||||
this.clock = clock;
|
||||
this.callback = callback;
|
||||
this.port = port;
|
||||
stateChange = new Semaphore(1);
|
||||
line = new byte[MAX_LINE_LENGTH];
|
||||
}
|
||||
|
||||
public boolean start() throws IOException {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Starting");
|
||||
try {
|
||||
stateChange.acquire();
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while waiting to start");
|
||||
}
|
||||
try {
|
||||
// Open the serial port
|
||||
port.openPort();
|
||||
// Find a suitable baud rate and initialise the modem
|
||||
try {
|
||||
boolean foundBaudRate = false;
|
||||
for(int baudRate : BAUD_RATES) {
|
||||
if(port.setParams(baudRate, 8, 1, 0)) {
|
||||
foundBaudRate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!foundBaudRate) {
|
||||
tryToClose(port);
|
||||
throw new IOException("No suitable baud rate");
|
||||
}
|
||||
port.purgePort(PURGE_RXCLEAR | PURGE_TXCLEAR);
|
||||
port.addEventListener(this);
|
||||
port.writeBytes("ATZ\r\n".getBytes("US-ASCII")); // Reset
|
||||
port.writeBytes("ATE0\r\n".getBytes("US-ASCII")); // Echo off
|
||||
} catch(IOException e) {
|
||||
tryToClose(port);
|
||||
throw e;
|
||||
}
|
||||
// Wait for the event thread to receive "OK"
|
||||
boolean success = false;
|
||||
try {
|
||||
synchronized(this) {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + OK_TIMEOUT;
|
||||
while(now < end && !initialised) {
|
||||
wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
success = initialised;
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while initialising");
|
||||
}
|
||||
if(success) return true;
|
||||
tryToClose(port);
|
||||
return false;
|
||||
} finally {
|
||||
stateChange.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(SerialPort port) {
|
||||
try {
|
||||
port.closePort();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Stopping");
|
||||
// Wake any threads that are waiting to connect
|
||||
synchronized(this) {
|
||||
initialised = false;
|
||||
connected = false;
|
||||
notifyAll();
|
||||
}
|
||||
// Hang up if necessary and close the port
|
||||
try {
|
||||
stateChange.acquire();
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while waiting to stop");
|
||||
}
|
||||
try {
|
||||
hangUpInner();
|
||||
port.closePort();
|
||||
} finally {
|
||||
stateChange.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: stateChange
|
||||
private void hangUpInner() throws IOException {
|
||||
ReliabilityLayer reliability;
|
||||
synchronized(this) {
|
||||
if(this.reliability == null) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not hanging up - already on the hook");
|
||||
return;
|
||||
}
|
||||
reliability = this.reliability;
|
||||
this.reliability = null;
|
||||
connected = false;
|
||||
}
|
||||
reliability.stop();
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Hanging up");
|
||||
try {
|
||||
clock.sleep(ESCAPE_SEQUENCE_GUARD_TIME);
|
||||
port.writeBytes("+++".getBytes("US-ASCII"));
|
||||
clock.sleep(ESCAPE_SEQUENCE_GUARD_TIME);
|
||||
port.writeBytes("ATH\r\n".getBytes("US-ASCII"));
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while hanging up");
|
||||
} catch(IOException e) {
|
||||
tryToClose(port);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean dial(String number) throws IOException {
|
||||
if(!stateChange.tryAcquire()) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not dialling - state change in progress");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ReliabilityLayer reliability =
|
||||
reliabilityFactory.createReliabilityLayer(this);
|
||||
synchronized(this) {
|
||||
if(!initialised) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not dialling - modem not initialised");
|
||||
return false;
|
||||
}
|
||||
if(this.reliability != null) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not dialling - call in progress");
|
||||
return false;
|
||||
}
|
||||
this.reliability = reliability;
|
||||
}
|
||||
reliability.start();
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Dialling");
|
||||
try {
|
||||
String dial = "ATDT" + number + "\r\n";
|
||||
port.writeBytes(dial.getBytes("US-ASCII"));
|
||||
} catch(IOException e) {
|
||||
tryToClose(port);
|
||||
throw e;
|
||||
}
|
||||
// Wait for the event thread to receive "CONNECT"
|
||||
try {
|
||||
synchronized(this) {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + CONNECT_TIMEOUT;
|
||||
while(now < end && initialised && !connected) {
|
||||
wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if(connected) return true;
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while dialling");
|
||||
}
|
||||
hangUpInner();
|
||||
return false;
|
||||
} finally {
|
||||
stateChange.release();
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
ReliabilityLayer reliability;
|
||||
synchronized(this) {
|
||||
reliability = this.reliability;
|
||||
}
|
||||
if(reliability == null) throw new IOException("Not connected");
|
||||
return reliability.getInputStream();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
ReliabilityLayer reliability;
|
||||
synchronized(this) {
|
||||
reliability = this.reliability;
|
||||
}
|
||||
if(reliability == null) throw new IOException("Not connected");
|
||||
return reliability.getOutputStream();
|
||||
}
|
||||
|
||||
public void hangUp() throws IOException {
|
||||
try {
|
||||
stateChange.acquire();
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while waiting to hang up");
|
||||
}
|
||||
try {
|
||||
hangUpInner();
|
||||
} finally {
|
||||
stateChange.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleWrite(byte[] b) throws IOException {
|
||||
try {
|
||||
port.writeBytes(b);
|
||||
} catch(IOException e) {
|
||||
tryToClose(port);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void serialEvent(SerialPortEvent ev) {
|
||||
try {
|
||||
if(ev.isRXCHAR()) {
|
||||
byte[] b = port.readBytes();
|
||||
if(!handleData(b)) handleText(b);
|
||||
} else if(ev.isDSR() && ev.getEventValue() == 0) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Remote end hung up");
|
||||
hangUp();
|
||||
} else {
|
||||
if(LOG.isLoggable(INFO)) {
|
||||
LOG.info("Serial event " + ev.getEventType() + " " +
|
||||
ev.getEventValue());
|
||||
}
|
||||
}
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleData(byte[] b) throws IOException {
|
||||
ReliabilityLayer reliability;
|
||||
synchronized(this) {
|
||||
reliability = this.reliability;
|
||||
}
|
||||
if(reliability == null) return false;
|
||||
reliability.handleRead(b);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleText(byte[] b) throws IOException {
|
||||
if(lineLen + b.length > MAX_LINE_LENGTH) {
|
||||
tryToClose(port);
|
||||
throw new IOException("Line too long");
|
||||
}
|
||||
for(int i = 0; i < b.length; i++) {
|
||||
line[lineLen] = b[i];
|
||||
if(b[i] == '\n') {
|
||||
String s = new String(line, 0, lineLen, "US-ASCII").trim();
|
||||
lineLen = 0;
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Modem status: " + s);
|
||||
if(s.startsWith("CONNECT")) {
|
||||
synchronized(this) {
|
||||
connected = true;
|
||||
notifyAll();
|
||||
}
|
||||
// There might be data in the buffer as well as text
|
||||
int off = i + 1;
|
||||
if(off < b.length) {
|
||||
byte[] data = new byte[b.length - off];
|
||||
System.arraycopy(b, off, data, 0, data.length);
|
||||
handleData(data);
|
||||
}
|
||||
return;
|
||||
} else if(s.equals("BUSY") || s.equals("NO DIALTONE")
|
||||
|| s.equals("NO CARRIER")) {
|
||||
synchronized(this) {
|
||||
connected = false;
|
||||
notifyAll();
|
||||
}
|
||||
} else if(s.equals("OK")) {
|
||||
synchronized(this) {
|
||||
initialised = true;
|
||||
notifyAll();
|
||||
}
|
||||
} else if(s.equals("RING")) {
|
||||
executor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
answer();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
lineLen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void answer() throws IOException {
|
||||
if(!stateChange.tryAcquire()) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not answering - state change in progress");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ReliabilityLayer reliability =
|
||||
reliabilityFactory.createReliabilityLayer(this);
|
||||
synchronized(this) {
|
||||
if(!initialised) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not answering - modem not initialised");
|
||||
return;
|
||||
}
|
||||
if(this.reliability != null) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Not answering - call in progress");
|
||||
return;
|
||||
}
|
||||
this.reliability = reliability;
|
||||
}
|
||||
reliability.start();
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Answering");
|
||||
try {
|
||||
port.writeBytes("ATA\r\n".getBytes("US-ASCII"));
|
||||
} catch(IOException e) {
|
||||
tryToClose(port);
|
||||
throw e;
|
||||
}
|
||||
// Wait for the event thread to receive "CONNECT"
|
||||
boolean success = false;
|
||||
try {
|
||||
synchronized(this) {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + CONNECT_TIMEOUT;
|
||||
while(now < end && initialised && !connected) {
|
||||
wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
success = connected;
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
tryToClose(port);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while answering");
|
||||
}
|
||||
if(success) callback.incomingCallConnected();
|
||||
else hangUpInner();
|
||||
} finally {
|
||||
stateChange.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
|
||||
static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("8f573867bedf54884b5868ee5d902832" +
|
||||
"ee5e522da84d0d431712bd672fbd2f79" +
|
||||
"262d27b93879b94ee9afbb80e7fc87fb");
|
||||
static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ModemPlugin.class.getName());
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final ModemFactory modemFactory;
|
||||
private final SerialPortList serialPortList;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxFrameLength;
|
||||
private final long maxLatency, pollingInterval;
|
||||
private final boolean shuffle; // Used to disable shuffling for testing
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile Modem modem = null;
|
||||
|
||||
ModemPlugin(Executor pluginExecutor, ModemFactory modemFactory,
|
||||
SerialPortList serialPortList, DuplexPluginCallback callback,
|
||||
int maxFrameLength, long maxLatency, long pollingInterval,
|
||||
boolean shuffle) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.modemFactory = modemFactory;
|
||||
this.serialPortList = serialPortList;
|
||||
this.callback = callback;
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
this.maxLatency = maxLatency;
|
||||
this.pollingInterval = pollingInterval;
|
||||
this.shuffle = shuffle;
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "MODEM_PLUGIN_NAME";
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return maxFrameLength;
|
||||
}
|
||||
|
||||
public long getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
for(String portName : serialPortList.getPortNames()) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Trying to initialise modem on " + portName);
|
||||
modem = modemFactory.createModem(this, portName);
|
||||
try {
|
||||
if(!modem.start()) continue;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Initialised modem on " + portName);
|
||||
running = true;
|
||||
return true;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
if(modem != null) {
|
||||
try {
|
||||
modem.stop();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean resetModem() {
|
||||
if(!running) return false;
|
||||
for(String portName : serialPortList.getPortNames()) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Trying to initialise modem on " + portName);
|
||||
modem = modemFactory.createModem(this, portName);
|
||||
try {
|
||||
if(!modem.start()) continue;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Initialised modem on " + portName);
|
||||
return true;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
running = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getPollingInterval() {
|
||||
return pollingInterval;
|
||||
}
|
||||
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if(!connected.isEmpty()) return; // One at a time please
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
poll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
if(!running) return;
|
||||
// Get the ISO 3166 code for the caller's country
|
||||
String callerIso = callback.getLocalProperties().get("iso3166");
|
||||
if(StringUtils.isNullOrEmpty(callerIso)) return;
|
||||
// Call contacts one at a time in a random order
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
List<ContactId> contacts = new ArrayList<ContactId>(remote.keySet());
|
||||
if(shuffle) Collections.shuffle(contacts);
|
||||
Iterator<ContactId> it = contacts.iterator();
|
||||
while(it.hasNext() && running) {
|
||||
ContactId c = it.next();
|
||||
// Get the ISO 3166 code for the callee's country
|
||||
TransportProperties properties = remote.get(c);
|
||||
if(properties == null) continue;
|
||||
String calleeIso = properties.get("iso3166");
|
||||
if(StringUtils.isNullOrEmpty(calleeIso)) continue;
|
||||
// Get the callee's phone number
|
||||
String number = properties.get("number");
|
||||
if(StringUtils.isNullOrEmpty(number)) continue;
|
||||
// Convert the number into direct dialling form
|
||||
number = CountryCodes.translate(number, callerIso, calleeIso);
|
||||
if(number == null) continue;
|
||||
// Dial the number
|
||||
try {
|
||||
if(!modem.dial(number)) continue;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
if(resetModem()) continue;
|
||||
break;
|
||||
}
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Outgoing call connected");
|
||||
ModemTransportConnection conn = new ModemTransportConnection();
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
try {
|
||||
conn.waitForDisposal();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Interrupted while polling");
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if(!running) return null;
|
||||
// Get the ISO 3166 code for the caller's country
|
||||
String fromIso = callback.getLocalProperties().get("iso3166");
|
||||
if(StringUtils.isNullOrEmpty(fromIso)) return null;
|
||||
// Get the ISO 3166 code for the callee's country
|
||||
TransportProperties properties = callback.getRemoteProperties().get(c);
|
||||
if(properties == null) return null;
|
||||
String toIso = properties.get("iso3166");
|
||||
if(StringUtils.isNullOrEmpty(toIso)) return null;
|
||||
// Get the callee's phone number
|
||||
String number = properties.get("number");
|
||||
if(StringUtils.isNullOrEmpty(number)) return null;
|
||||
// Convert the number into direct dialling form
|
||||
number = CountryCodes.translate(number, fromIso, toIso);
|
||||
if(number == null) return null;
|
||||
// Dial the number
|
||||
try {
|
||||
if(!modem.dial(number)) return null;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
resetModem();
|
||||
return null;
|
||||
}
|
||||
return new ModemTransportConnection();
|
||||
}
|
||||
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void incomingCallConnected() {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Incoming call connected");
|
||||
callback.incomingConnectionCreated(new ModemTransportConnection());
|
||||
}
|
||||
|
||||
private class ModemTransportConnection
|
||||
implements DuplexTransportConnection {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return maxFrameLength;
|
||||
}
|
||||
|
||||
public long getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return modem.getInputStream();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return modem.getOutputStream();
|
||||
}
|
||||
|
||||
public boolean shouldFlush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dispose(boolean exception, boolean recognised) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Call disconnected");
|
||||
try {
|
||||
modem.hangUp();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
exception = true;
|
||||
}
|
||||
if(exception) resetModem();
|
||||
finished.countDown();
|
||||
}
|
||||
|
||||
private void waitForDisposal() throws InterruptedException {
|
||||
finished.await();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
public class ModemPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_FRAME_LENGTH = 1024;
|
||||
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
|
||||
private static final long POLLING_INTERVAL = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final ModemFactory modemFactory;
|
||||
private final SerialPortList serialPortList;
|
||||
|
||||
public ModemPluginFactory(Executor pluginExecutor,
|
||||
ReliabilityLayerFactory reliabilityFactory) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
modemFactory = new ModemFactoryImpl(pluginExecutor, reliabilityFactory);
|
||||
serialPortList = new SerialPortListImpl();
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return ModemPlugin.ID;
|
||||
}
|
||||
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
// This plugin is not enabled by default
|
||||
String enabled = callback.getConfig().get("enabled");
|
||||
if(StringUtils.isNullOrEmpty(enabled)) return null;
|
||||
return new ModemPlugin(pluginExecutor, modemFactory, serialPortList,
|
||||
callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL,
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jssc.SerialPortEventListener;
|
||||
|
||||
interface SerialPort {
|
||||
|
||||
void openPort() throws IOException;
|
||||
|
||||
void closePort() throws IOException;
|
||||
|
||||
boolean setParams(int baudRate, int dataBits, int stopBits, int parityBits)
|
||||
throws IOException;
|
||||
|
||||
void purgePort(int flags) throws IOException;
|
||||
|
||||
void addEventListener(SerialPortEventListener l) throws IOException;
|
||||
|
||||
byte[] readBytes() throws IOException;
|
||||
|
||||
void writeBytes(byte[] b) throws IOException;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jssc.SerialPortEventListener;
|
||||
import jssc.SerialPortException;
|
||||
|
||||
class SerialPortImpl implements SerialPort {
|
||||
|
||||
private final jssc.SerialPort port;
|
||||
|
||||
SerialPortImpl(String portName) {
|
||||
port = new jssc.SerialPort(portName);
|
||||
}
|
||||
|
||||
public void openPort() throws IOException {
|
||||
try {
|
||||
if(!port.openPort()) throw new IOException("Failed to open port");
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void closePort() throws IOException {
|
||||
try {
|
||||
if(!port.closePort()) throw new IOException("Failed to close port");
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setParams(int baudRate, int dataBits, int stopBits,
|
||||
int parityBits) throws IOException {
|
||||
try {
|
||||
return port.setParams(baudRate, dataBits, stopBits, parityBits);
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void purgePort(int flags) throws IOException {
|
||||
try {
|
||||
if(!port.purgePort(flags))
|
||||
throw new IOException("Failed to purge port");
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void addEventListener(SerialPortEventListener l) throws IOException {
|
||||
try {
|
||||
port.addEventListener(l);
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] readBytes() throws IOException {
|
||||
try {
|
||||
return port.readBytes();
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBytes(byte[] b) throws IOException {
|
||||
try {
|
||||
if(!port.writeBytes(b)) throw new IOException("Failed to write");
|
||||
} catch(SerialPortException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
interface SerialPortList {
|
||||
|
||||
String[] getPortNames();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package net.sf.briar.plugins.modem;
|
||||
|
||||
class SerialPortListImpl implements SerialPortList {
|
||||
|
||||
public String[] getPortNames() {
|
||||
return jssc.SerialPortList.getPortNames();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user