diff --git a/.classpath b/.classpath
index 6c5b256cb..c9078a1e4 100644
--- a/.classpath
+++ b/.classpath
@@ -20,7 +20,7 @@
-
+
diff --git a/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java
index 001f8a3f0..fa6eb7833 100644
--- a/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java
+++ b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java
@@ -15,14 +15,18 @@ class ShutdownManagerImpl implements ShutdownManager {
hooks = new HashMap();
}
- public synchronized int addShutdownHook(Runnable runnable) {
+ public synchronized int addShutdownHook(Runnable r) {
int handle = nextHandle++;
- Thread hook = new Thread(runnable);
+ Thread hook = createThread(r);
hooks.put(handle, hook);
Runtime.getRuntime().addShutdownHook(hook);
return handle;
}
+ protected Thread createThread(Runnable r) {
+ return new Thread(r);
+ }
+
public synchronized boolean removeShutdownHook(int handle) {
Thread hook = hooks.remove(handle);
if(hook == null) return false;
diff --git a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
index 7d8939c34..7fd9ec391 100644
--- a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
+++ b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
@@ -1,21 +1,27 @@
package net.sf.briar.lifecycle;
-import java.awt.HeadlessException;
+import java.util.Map;
+import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.swing.JFrame;
-
import net.sf.briar.util.OsUtils;
+import com.sun.jna.Library;
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 {
@@ -23,36 +29,36 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
Logger.getLogger(WindowsShutdownManagerImpl.class.getName());
private static final int WM_QUERYENDSESSION = 17;
- private static final int WM_ENDSESSION = 22;
private static final int GWL_WNDPROC = -4;
+ private static final int WS_MINIMIZE = 0x20000000;
+
+ private final Map options;
private boolean initialised = false; // Locking: this
+ WindowsShutdownManagerImpl() {
+ // Use the Unicode versions of Win32 API calls
+ options = new TreeMap();
+ options.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
+ options.put(Library.OPTION_FUNCTION_MAPPER,
+ W32APIFunctionMapper.UNICODE);
+ }
+
@Override
- public synchronized int addShutdownHook(Runnable runnable) {
+ public synchronized int addShutdownHook(Runnable r) {
if(!initialised) initialise();
- return super.addShutdownHook(new RunOnce(runnable));
+ return super.addShutdownHook(r);
+ }
+
+ @Override
+ protected Thread createThread(Runnable r) {
+ return new StartOnce(r);
}
// Locking: this
private void initialise() {
if(OsUtils.isWindows()) {
- try {
- HWND hwnd = new HWND();
- hwnd.setPointer(Native.getComponentPointer(new JFrame()));
- User32 u = (User32) Native.loadLibrary("user32", User32.class);
- try {
- // Load the 64-bit functions
- setCallback64Bit(u, hwnd);
- } catch(UnsatisfiedLinkError e) {
- // Load the 32-bit functions
- setCallback32Bit(u, hwnd);
- }
- } catch(HeadlessException e) {
- if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
- } catch(UnsatisfiedLinkError e) {
- if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
- }
+ new EventLoop().start();
} else {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Windows shutdown manager used on non-Windows OS");
@@ -60,38 +66,6 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
initialised = true;
}
- private void setCallback64Bit(final User32 user32, HWND hwnd) {
- final WindowProc oldProc = user32.GetWindowLongPtrW(hwnd, GWL_WNDPROC);
- WindowProc newProc = 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();
- } else if(msg == WM_ENDSESSION) {
- // Return immediately or the JVM crashes on return
- }
- return user32.CallWindowProcPtrW(oldProc, hwnd, msg, wp, lp);
- }
- };
- user32.SetWindowLongPtrW(hwnd, GWL_WNDPROC, newProc);
- }
-
- private void setCallback32Bit(final User32 user32, HWND hwnd) {
- final WindowProc oldProc = user32.GetWindowLongW(hwnd, GWL_WNDPROC);
- WindowProc newProc = 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();
- } else if(msg == WM_ENDSESSION) {
- // Return immediately or the JVM crashes on return
- }
- return user32.CallWindowProcW(oldProc, hwnd, msg, wp, lp);
- }
- };
- user32.SetWindowLongW(hwnd, GWL_WNDPROC, newProc);
- }
-
// Package access for testing
synchronized void runShutdownHooks() {
// Start each hook in its own thread
@@ -106,33 +80,81 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
}
}
- private static class RunOnce implements Runnable {
+ private class EventLoop extends Thread {
+
+ @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(Level.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(Level.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(Level.WARNING)) LOG.warning(e.getMessage());
+ }
+ }
+ }
+
+ private static class StartOnce extends Thread {
- private final Runnable runnable;
private final AtomicBoolean called = new AtomicBoolean(false);
- private RunOnce(Runnable runnable) {
- this.runnable = runnable;
+ private StartOnce(Runnable r) {
+ super(r);
}
- public void run() {
- if(called.getAndSet(true)) return;
- runnable.run();
+ @Override
+ public void start() {
+ // Ensure the thread is only started once
+ if(!called.getAndSet(true)) super.start();
}
}
private static interface User32 extends StdCallLibrary {
- LRESULT CallWindowProcW(WindowProc oldProc, HWND hwnd, int msg,
- WPARAM wp, LPARAM lp);
- LRESULT CallWindowProcPtrW(WindowProc oldProc, HWND hwnd, int msg,
- WPARAM wp, LPARAM lp);
+ 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);
- WindowProc GetWindowLongW(HWND hwnd, int index);
- WindowProc GetWindowLongPtrW(HWND hwnd, int index);
+ 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);
- LRESULT SetWindowLongW(HWND hwnd, int index, WindowProc newProc);
- LRESULT SetWindowLongPtrW(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 {
diff --git a/components/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java b/components/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java
index f42d322c7..92850a4e0 100644
--- a/components/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java
+++ b/components/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java
@@ -18,9 +18,14 @@ public class RemovableDrivePluginFactory implements BatchPluginFactory {
if(OsUtils.isLinux()) {
finder = new LinuxRemovableDriveFinder();
monitor = new LinuxRemovableDriveMonitor();
- } else if(OsUtils.isMac()) {
+ } 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(finder,
+ POLLING_INTERVAL);
} else if(OsUtils.isWindows()) {
finder = new WindowsRemovableDriveFinder();
monitor = new PollingRemovableDriveMonitor(finder,
diff --git a/lib/h2small-1.3.161.jar b/lib/h2small-1.3.161.jar
new file mode 100644
index 000000000..b209bed31
Binary files /dev/null and b/lib/h2small-1.3.161.jar differ
diff --git a/lib/h2small-snapshot-2011-10-25.jar b/lib/h2small-snapshot-2011-10-25.jar
deleted file mode 100644
index 85821fc58..000000000
Binary files a/lib/h2small-snapshot-2011-10-25.jar and /dev/null differ
diff --git a/test/build.xml b/test/build.xml
index b3c7132f5..e7df24043 100644
--- a/test/build.xml
+++ b/test/build.xml
@@ -27,6 +27,7 @@
+
diff --git a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java
index fc2482c9f..a7be414c9 100644
--- a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java
+++ b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java
@@ -12,7 +12,7 @@ public class ShutdownManagerImplTest extends TestCase {
@Test
public void testAddAndRemove() {
- ShutdownManager s = new ShutdownManagerImpl();
+ ShutdownManager s = createShutdownManager();
Set handles = new HashSet();
for(int i = 0; i < 100; i++) {
int handle = s.addShutdownHook(new Runnable() {
@@ -26,4 +26,8 @@ public class ShutdownManagerImplTest extends TestCase {
// The hooks should no longer be removable
for(int handle : handles) assertFalse(s.removeShutdownHook(handle));
}
+
+ protected ShutdownManager createShutdownManager() {
+ return new ShutdownManagerImpl();
+ }
}
diff --git a/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java
new file mode 100644
index 000000000..42795b4ca
--- /dev/null
+++ b/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java
@@ -0,0 +1,39 @@
+package net.sf.briar.lifecycle;
+
+import net.sf.briar.api.lifecycle.ShutdownManager;
+
+import org.junit.Test;
+
+public class WindowsShutdownManagerImplTest extends ShutdownManagerImplTest {
+
+ @Override
+ protected ShutdownManager createShutdownManager() {
+ return new WindowsShutdownManagerImpl();
+ }
+
+ @Test
+ public void testManagerWaitsForHooksToRun() {
+ WindowsShutdownManagerImpl s = new WindowsShutdownManagerImpl();
+ SlowHook[] hooks = new SlowHook[10];
+ for(int i = 0; i < hooks.length; i++) {
+ hooks[i] = new SlowHook();
+ s.addShutdownHook(hooks[i]);
+ }
+ s.runShutdownHooks();
+ for(int i = 0; i < hooks.length; i++) assertTrue(hooks[i].finished);
+ }
+
+ private static class SlowHook implements Runnable {
+
+ private volatile boolean finished = false;
+
+ public void run() {
+ try {
+ Thread.sleep(100);
+ finished = true;
+ } catch(InterruptedException e) {
+ // Don't finish
+ }
+ }
+ }
+}
diff --git a/test/net/sf/briar/plugins/PluginManagerImplTest.java b/test/net/sf/briar/plugins/PluginManagerImplTest.java
index 0a1b4693f..276f16236 100644
--- a/test/net/sf/briar/plugins/PluginManagerImplTest.java
+++ b/test/net/sf/briar/plugins/PluginManagerImplTest.java
@@ -41,9 +41,12 @@ public class PluginManagerImplTest extends TestCase {
Poller poller = new PollerImpl();
PluginManagerImpl p = new PluginManagerImpl(db, executor, 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());
+ // We expect either 2 or 3 plugins to be started, depending on whether
+ // the test machine has a Bluetooth device
+ int started = p.startPlugins();
+ int stopped = p.stopPlugins();
+ assertEquals(started, stopped);
+ assertTrue(started >= 2);
+ assertTrue(started <= 3);
}
}
diff --git a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java b/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
index 1ed814d03..c320968d0 100644
--- a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
+++ b/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
@@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
+import net.sf.briar.util.OsUtils;
import org.junit.After;
import org.junit.Before;
@@ -25,6 +26,10 @@ public class UnixRemovableDriveMonitorTest extends TestCase {
@Test
public void testNonexistentDir() throws Exception {
+ if(!OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer()) {
+ System.err.println("Warning: Skipping test");
+ return;
+ }
File doesNotExist = new File(testDir, "doesNotExist");
RemovableDriveMonitor monitor = createMonitor(doesNotExist);
monitor.start(null);
@@ -33,6 +38,10 @@ public class UnixRemovableDriveMonitorTest extends TestCase {
@Test
public void testOneCallbackPerFile() throws Exception {
+ if(!OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer()) {
+ System.err.println("Warning: Skipping test");
+ return;
+ }
// Create a callback that will wait for two files before stopping
final List detected = new ArrayList();
final CountDownLatch latch = new CountDownLatch(2);
diff --git a/util/net/sf/briar/util/OsUtils.java b/util/net/sf/briar/util/OsUtils.java
index f8e8553da..dd32ad897 100644
--- a/util/net/sf/briar/util/OsUtils.java
+++ b/util/net/sf/briar/util/OsUtils.java
@@ -3,6 +3,7 @@ package net.sf.briar.util;
public class OsUtils {
private static final String os = System.getProperty("os.name");
+ private static final String version = System.getProperty("os.version");
public static boolean isWindows() {
return os.indexOf("Windows") != -1;
@@ -12,6 +13,19 @@ public class OsUtils {
return os.indexOf("Mac OS") != -1;
}
+ public static boolean isMacLeopardOrNewer() {
+ if(!isMac() || version == null) return false;
+ try {
+ String[] v = version.split("\\.");
+ if(v.length != 3) return false;
+ int major = Integer.parseInt(v[0]);
+ int minor = Integer.parseInt(v[1]);
+ return major >= 10 && minor >= 5;
+ } catch(NumberFormatException e) {
+ return false;
+ }
+ }
+
public static boolean isLinux() {
return os.indexOf("Linux") != -1;
}