diff --git a/components/net/sf/briar/plugins/file/LinuxRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/LinuxRemovableDriveMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..71a3e838ae3786861d79b8bbbddfa7fca2894664
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/LinuxRemovableDriveMonitor.java
@@ -0,0 +1,9 @@
+package net.sf.briar.plugins.file;
+
+class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
+
+	@Override
+	protected String[] getPathsToWatch() {
+		return new String[] { "/mnt", "/media" };
+	}
+}
diff --git a/components/net/sf/briar/plugins/file/MacRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/MacRemovableDriveMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2886facbc6cc56d73edc2c673979110c1c942a7
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/MacRemovableDriveMonitor.java
@@ -0,0 +1,9 @@
+package net.sf.briar.plugins.file;
+
+class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
+
+	@Override
+	protected String[] getPathsToWatch() {
+		return new String[] { "/Volumes" };
+	}
+}
diff --git a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..f693aa6a9985dfb1c1c5d220406d3bd4877709a7
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
@@ -0,0 +1,89 @@
+package net.sf.briar.plugins.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
+
+	private final RemovableDriveFinder finder;
+	private final long pollingInterval;
+	private final LinkedList<File> inserted;
+	private final LinkedList<IOException> exceptions;
+	private final Object pollingLock;
+
+	private boolean started = false, stopped = false;
+	private Thread pollingThread = null;
+
+	public PollingRemovableDriveMonitor(RemovableDriveFinder finder,
+			long pollingInterval) {
+		this.finder = finder;
+		this.pollingInterval = pollingInterval;
+		inserted = new LinkedList<File>();
+		exceptions = new LinkedList<IOException>();
+		pollingLock = new Object();
+	}
+
+	public synchronized void start() throws IOException {
+		if(started || stopped) throw new IllegalStateException();
+		started = true;
+		pollingThread = new Thread(this);
+		pollingThread.start();
+	}
+
+	public synchronized File waitForInsertion() throws IOException {
+		if(!started || stopped) throw new IllegalStateException();
+		if(!exceptions.isEmpty()) throw exceptions.poll();
+		while(inserted.isEmpty()) {
+			try {
+				wait();
+			} catch(InterruptedException ignored) {}
+			if(!exceptions.isEmpty()) throw exceptions.poll();
+		}
+		return inserted.poll();
+	}
+
+	public synchronized void stop() throws IOException {
+		if(!started || stopped) throw new IllegalStateException();
+		if(!exceptions.isEmpty()) throw exceptions.poll();
+		stopped = true;
+		synchronized(pollingLock) {
+			pollingLock.notifyAll();
+		}
+	}
+
+	public void run() {
+		try {
+			List<File> drives = finder.findRemovableDrives();
+			while(true) {
+				synchronized(this) {
+					if(stopped) return;
+				}
+				synchronized(pollingLock) {
+					try {
+						pollingLock.wait(pollingInterval);
+					} catch(InterruptedException ignored) {}
+				}
+				synchronized(this) {
+					if(stopped) return;
+				}
+				List<File> newDrives = finder.findRemovableDrives();
+				for(File f : newDrives) {
+					if(!drives.contains(f)) {
+						synchronized(this) {
+							inserted.add(f);
+							notifyAll();
+						}
+					}
+				}
+				drives = newDrives;
+			}
+		} catch(IOException e) {
+			synchronized(this) {
+				exceptions.add(e);
+				notifyAll();
+			}
+		}
+	}
+}
diff --git a/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..3ce0cc5f27b716edfb44e00ca206b28f9994fcd9
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java
@@ -0,0 +1,13 @@
+package net.sf.briar.plugins.file;
+
+import java.io.File;
+import java.io.IOException;
+
+interface RemovableDriveMonitor {
+
+	void start() throws IOException;
+
+	File waitForInsertion() throws IOException;
+
+	void stop() throws IOException;
+}
diff --git a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a211cc509063e41839fc1d5c73195a2eac4ed1b
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
@@ -0,0 +1,67 @@
+package net.sf.briar.plugins.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.contentobjects.jnotify.JNotify;
+import net.contentobjects.jnotify.JNotifyListener;
+
+abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
+JNotifyListener {
+
+	private final List<Integer> watches = new ArrayList<Integer>();
+	private final LinkedList<File> inserted = new LinkedList<File>();
+
+	private boolean started = false, stopped = false;
+
+	protected abstract String[] getPathsToWatch();
+
+	public synchronized void start() throws IOException {
+		if(started || stopped) throw new IllegalStateException();
+		started = true;
+		int mask = JNotify.FILE_CREATED;
+		for(String path : getPathsToWatch()) {
+			if(new File(path).exists())
+				watches.add(JNotify.addWatch(path, mask, false, this));
+		}
+	}
+
+	public synchronized File waitForInsertion() throws IOException {
+		if(!started || stopped) throw new IllegalStateException();
+		while(inserted.isEmpty()) {
+			try {
+				wait();
+			} catch(InterruptedException ignored) {}
+		}
+		return inserted.poll();
+	}
+
+	public synchronized void stop() throws IOException {
+		if(!started || stopped) throw new IllegalStateException();
+		stopped = true;
+		for(Integer w : watches) JNotify.removeWatch(w);
+	}
+
+	public void fileCreated(int wd, String rootPath, String name) {
+		synchronized(this) {
+			inserted.add(new File(rootPath + "/" + name));
+			notifyAll();
+		}
+	}
+
+	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();
+	}
+}