diff --git a/.classpath b/.classpath index 6c5b256cb74c52fb9f102b370d4a2439648a1466..c9078a1e45aef4e78ebf61648ea5fab3090e441a 100644 --- a/.classpath +++ b/.classpath @@ -20,7 +20,7 @@ <classpathentry kind="lib" path="lib/platform.jar"/> <classpathentry kind="lib" path="lib/jnotify-0.93.jar"/> <classpathentry kind="lib" path="lib/bluecove-gpl-2.1.0.jar" sourcepath="lib/source/bluecove-gpl-2.1.0-sources.jar"/> - <classpathentry kind="lib" path="lib/h2small-snapshot-2011-10-25.jar"/> <classpathentry kind="lib" path="lib/bluecove-2.1.0-briar.jar" sourcepath="lib/source/bluecove-2.1.0-briar-sources.jar"/> + <classpathentry kind="lib" path="lib/h2small-1.3.161.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java index 001f8a3f0a4a2cbf03770e1360d99e59ff7fc7b7..fa6eb7833fdb04f31be15f71bcc75be30ad7275c 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<Integer, Thread>(); } - 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 7d8939c340365038da773a0f0a5a3113c4153754..7fd9ec3914b1f611f7815e5fd8247cbcecd414f5 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<String, Object> options; private boolean initialised = false; // Locking: this + WindowsShutdownManagerImpl() { + // Use the Unicode versions of Win32 API calls + options = new TreeMap<String, Object>(); + 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 f42d322c7753e51a1f40e69ba57584e6c03b2d00..92850a4e03a89e22ad96fb1b6fb99e5b341d6e25 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 0000000000000000000000000000000000000000..b209bed31c7ecf658d4d930b8552d72e660f9c79 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 85821fc58c0394144a60c60ecd8c738d49012595..0000000000000000000000000000000000000000 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 b3c7132f56dd40102bc7cb3a674430df9239ba7e..e7df24043d059788a956d140e7a82852ad623db9 100644 --- a/test/build.xml +++ b/test/build.xml @@ -27,6 +27,7 @@ <test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/> <test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/> + <test name='net.sf.briar.lifecycle.WindowsShutdownManagerImplTest'/> <test name='net.sf.briar.plugins.PluginManagerImplTest'/> <test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/> <test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/> diff --git a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java index fc2482c9fa300e2118bca22a6fca1660e3391b40..a7be414c9611dc1d21fb6a64bbd7d0f885be1e25 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<Integer> handles = new HashSet<Integer>(); 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 0000000000000000000000000000000000000000..42795b4ca2b80ab81f5cb5cf361e05a57012a5a0 --- /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 0a1b4693ff88dd152693fcdc11a458c67541c17d..276f16236a9019253c39538350a8d0dcf64c3822 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 1ed814d038dfb03eccebcb07ddac128616d96b44..c320968d0ed687067f30bd52f26d2afebe478192 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<File> detected = new ArrayList<File>(); 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 f8e8553da5ca15ee90872b8eb8a12a191b8b680d..dd32ad897fbf4314b446fe49c16ae8c63aa8dece 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; }