From c4692a7007ecb8a43e0a334fff1ba027c2be4f1c Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Wed, 20 Jan 2016 12:03:15 +0000
Subject: [PATCH] Identity manager hooks. #209

---
 .../org/briarproject/api/contact/Contact.java |  3 +-
 .../briarproject/api/contact/ContactId.java   |  3 +-
 .../api/db/DatabaseComponent.java             |  7 ++
 .../api/identity/IdentityManager.java         | 14 ++++
 .../api/identity/LocalAuthor.java             | 33 +++++++-
 .../contact/ContactManagerImpl.java           | 14 +++-
 .../briarproject/contact/ContactModule.java   |  3 +
 .../src/org/briarproject/db/Database.java     | 14 +++-
 .../db/DatabaseComponentImpl.java             | 47 +++++++++--
 .../src/org/briarproject/db/JdbcDatabase.java | 46 +++++++---
 .../identity/IdentityManagerImpl.java         | 84 ++++++++++++++++++-
 .../briarproject/sync/AuthorFactoryImpl.java  |  4 +-
 .../db/DatabaseComponentImplTest.java         | 17 ++--
 .../org/briarproject/db/H2DatabaseTest.java   |  9 +-
 .../org/briarproject/sync/ConstantsTest.java  |  5 +-
 .../sync/SimplexMessagingIntegrationTest.java |  6 +-
 16 files changed, 257 insertions(+), 52 deletions(-)

diff --git a/briar-api/src/org/briarproject/api/contact/Contact.java b/briar-api/src/org/briarproject/api/contact/Contact.java
index 585010ef74..62d7efa643 100644
--- a/briar-api/src/org/briarproject/api/contact/Contact.java
+++ b/briar-api/src/org/briarproject/api/contact/Contact.java
@@ -61,7 +61,6 @@ public class Contact {
 
 	@Override
 	public boolean equals(Object o) {
-		if (o instanceof Contact) return id.equals(((Contact) o).id);
-		return false;
+		return o instanceof Contact && id.equals(((Contact) o).id);
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/contact/ContactId.java b/briar-api/src/org/briarproject/api/contact/ContactId.java
index 1805818d7c..736597a388 100644
--- a/briar-api/src/org/briarproject/api/contact/ContactId.java
+++ b/briar-api/src/org/briarproject/api/contact/ContactId.java
@@ -23,7 +23,6 @@ public class ContactId {
 
 	@Override
 	public boolean equals(Object o) {
-		if (o instanceof ContactId) return id == ((ContactId) o).id;
-		return false;
+		return o instanceof ContactId && id == ((ContactId) o).id;
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 4761982310..b2ee63bde1 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -152,6 +152,9 @@ public interface DatabaseComponent {
 	/** Returns all contacts. */
 	Collection<Contact> getContacts() throws DbException;
 
+	/** Returns all contacts associated with the given local pseudonym. */
+	Collection<ContactId> getContacts(AuthorId a) throws DbException;
+
 	/** Returns the group with the given ID, if the user subscribes to it. */
 	Group getGroup(GroupId g) throws DbException;
 
@@ -300,6 +303,10 @@ public interface DatabaseComponent {
 	/** Sets the status of the given contact. */
 	void setContactStatus(ContactId c, Contact.Status s) throws DbException;
 
+	/** Sets the status of the given local pseudonym. */
+	void setLocalAuthorStatus(AuthorId a, LocalAuthor.Status s)
+		throws DbException;
+
 	/** Marks the given message as valid or invalid. */
 	void setMessageValidity(Message m, ClientId c, boolean valid)
 			throws DbException;
diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
index 207fea87d5..ec2383371b 100644
--- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java
+++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
@@ -6,6 +6,12 @@ import java.util.Collection;
 
 public interface IdentityManager {
 
+	/** Registers a hook to be called whenever a local pseudonym is added. */
+	void registerIdentityAddedHook(IdentityAddedHook hook);
+
+	/** Registers a hook to be called whenever a local pseudonym is removed. */
+	void registerIdentityRemovedHook(IdentityRemovedHook hook);
+
 	/** Stores a local pseudonym. */
 	void addLocalAuthor(LocalAuthor a) throws DbException;
 
@@ -17,4 +23,12 @@ public interface IdentityManager {
 
 	/** Removes a local pseudonym and all associated state. */
 	void removeLocalAuthor(AuthorId a) throws DbException;
+
+	interface IdentityAddedHook {
+		void identityAdded(AuthorId a);
+	}
+
+	interface IdentityRemovedHook {
+		void identityRemoved(AuthorId a);
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/identity/LocalAuthor.java b/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
index 9f5626d77b..da5d4a908c 100644
--- a/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
+++ b/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
@@ -3,14 +3,36 @@ package org.briarproject.api.identity;
 /** A pseudonym for the local user. */
 public class LocalAuthor extends Author {
 
+	public enum Status {
+
+		ADDING(0), ACTIVE(1), REMOVING(2);
+
+		private final int value;
+
+		Status(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public static Status fromValue(int value) {
+			for (Status s : values()) if (s.value == value) return s;
+			throw new IllegalArgumentException();
+		}
+	}
+
 	private final byte[] privateKey;
 	private final long created;
+	private final Status status;
 
 	public LocalAuthor(AuthorId id, String name, byte[] publicKey,
-			byte[] privateKey, long created) {
+			byte[] privateKey, long created, Status status) {
 		super(id, name, publicKey);
 		this.privateKey = privateKey;
 		this.created = created;
+		this.status = status;
 	}
 
 	/**  Returns the private key used to generate the pseudonym's signatures. */
@@ -18,7 +40,16 @@ public class LocalAuthor extends Author {
 		return privateKey;
 	}
 
+	/**
+	 * Returns the time the pseudonym was created, in milliseconds since the
+	 * Unix epoch.
+	 */
 	public long getTimeCreated() {
 		return created;
 	}
+
+	/** Returns the status of the pseudonym. */
+	public Status getStatus() {
+		return status;
+	}
 }
diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
index 3eb37298d4..92d8020303 100644
--- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
@@ -13,6 +13,7 @@ import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager.IdentityRemovedHook;
 import org.briarproject.api.lifecycle.Service;
 
 import java.util.ArrayList;
@@ -27,7 +28,8 @@ import static org.briarproject.api.contact.Contact.Status.ACTIVE;
 import static org.briarproject.api.contact.Contact.Status.ADDING;
 import static org.briarproject.api.contact.Contact.Status.REMOVING;
 
-class ContactManagerImpl implements ContactManager, Service {
+class ContactManagerImpl implements ContactManager, Service,
+		IdentityRemovedHook {
 
 	private static final Logger LOG =
 			Logger.getLogger(ContactManagerImpl.class.getName());
@@ -118,4 +120,14 @@ class ContactManagerImpl implements ContactManager, Service {
 		db.removeContact(c);
 		eventBus.broadcast(new ContactRemovedEvent(c));
 	}
+
+	@Override
+	public void identityRemoved(AuthorId a) {
+		// Remove any contacts of the local pseudonym that's being removed
+		try {
+			for (ContactId c : db.getContacts(a)) removeContact(c);
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
 }
diff --git a/briar-core/src/org/briarproject/contact/ContactModule.java b/briar-core/src/org/briarproject/contact/ContactModule.java
index 105aa9ef15..3a374ab5e3 100644
--- a/briar-core/src/org/briarproject/contact/ContactModule.java
+++ b/briar-core/src/org/briarproject/contact/ContactModule.java
@@ -4,6 +4,7 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
 import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.lifecycle.LifecycleManager;
 
 import javax.inject.Singleton;
@@ -15,8 +16,10 @@ public class ContactModule extends AbstractModule {
 
 	@Provides @Singleton
 	ContactManager getContactManager(LifecycleManager lifecycleManager,
+			IdentityManager identityManager,
 			ContactManagerImpl contactManager) {
 		lifecycleManager.register(contactManager);
+		identityManager.registerIdentityRemovedHook(contactManager);
 		return contactManager;
 	}
 }
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index 39790deedc..65518cc5f4 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -334,7 +334,7 @@ interface Database<T> {
 	 * Locking: read
 	 */
 	Collection<MessageStatus> getMessageStatus(T txn, ContactId c, GroupId g)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the status of the given message with respect to the given
@@ -343,7 +343,7 @@ interface Database<T> {
 	 * Locking: read
 	 */
 	MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the IDs of some messages received from the given contact that
@@ -388,7 +388,7 @@ interface Database<T> {
 	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the message with the given ID, in serialised form.
@@ -639,6 +639,14 @@ interface Database<T> {
 	void setContactStatus(T txn, ContactId c, Contact.Status s)
 			throws DbException;
 
+	/**
+	 * Sets the status of the given local pseudonym.
+	 * <p>
+	 * Locking: write.
+	 */
+	void setLocalAuthorStatus(T txn, AuthorId a, LocalAuthor.Status s)
+			throws DbException;
+
 	/**
 	 * Marks the given message as valid or invalid.
 	 * <p>
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 546434ff5a..13d650912d 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -16,10 +16,7 @@ import org.briarproject.api.db.NoSuchLocalAuthorException;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
-import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.LocalAuthorAddedEvent;
-import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
@@ -218,7 +215,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	public void addLocalMessage(Message m, ClientId c, Metadata meta)
@@ -572,6 +568,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
+	public Collection<ContactId> getContacts(AuthorId a) throws DbException {
+		lock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if (!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				Collection<ContactId> contacts = db.getContacts(txn, a);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
 	public Group getGroup(GroupId g) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -1242,14 +1257,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	}
 
 	public void removeLocalAuthor(AuthorId a) throws DbException {
-		Collection<ContactId> affected;
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
 				if (!db.containsLocalAuthor(txn, a))
 					throw new NoSuchLocalAuthorException();
-				affected = db.getContacts(txn, a);
 				db.removeLocalAuthor(txn, a);
 				db.commitTransaction(txn);
 			} catch (DbException e) {
@@ -1259,9 +1272,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		for (ContactId c : affected)
-			eventBus.broadcast(new ContactRemovedEvent(c));
-		eventBus.broadcast(new LocalAuthorRemovedEvent(a));
 	}
 
 	public void removeTransport(TransportId t) throws DbException {
@@ -1302,6 +1312,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
+	public void setLocalAuthorStatus(AuthorId a, LocalAuthor.Status s)
+			throws DbException {
+		lock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if (!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				db.setLocalAuthorStatus(txn, a, s);
+				db.commitTransaction(txn);
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
 	public void setMessageValidity(Message m, ClientId c, boolean valid)
 			throws DbException {
 		lock.writeLock().lock();
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 1faf11bda2..54be105b24 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -64,8 +64,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 15;
-	private static final int MIN_SCHEMA_VERSION = 15;
+	private static final int SCHEMA_VERSION = 16;
+	private static final int MIN_SCHEMA_VERSION = 16;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
@@ -81,6 +81,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " publicKey BINARY NOT NULL,"
 					+ " privateKey BINARY NOT NULL,"
 					+ " created BIGINT NOT NULL,"
+					+ " status INT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
 	private static final String CREATE_CONTACTS =
@@ -719,15 +720,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 			throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "INSERT INTO localAuthors"
-					+ " (authorId, name, publicKey, privateKey, created)"
-					+ " VALUES (?, ?, ?, ?, ?)";
+			String sql = "INSERT INTO localAuthors (authorId, name, publicKey,"
+					+ " privateKey, created, status)"
+					+ " VALUES (?, ?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, a.getId().getBytes());
 			ps.setString(2, a.getName());
 			ps.setBytes(3, a.getPublicKey());
 			ps.setBytes(4, a.getPrivateKey());
 			ps.setLong(5, a.getTimeCreated());
+			ps.setInt(6, a.getStatus().getValue());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -1345,7 +1347,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT name, publicKey, privateKey, created"
+			String sql = "SELECT name, publicKey, privateKey, created, status"
 					+ " FROM localAuthors"
 					+ " WHERE authorId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1356,8 +1358,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 			byte[] publicKey = rs.getBytes(2);
 			byte[] privateKey = rs.getBytes(3);
 			long created = rs.getLong(4);
+			LocalAuthor.Status status = LocalAuthor.Status.fromValue(
+					rs.getInt(5));
 			LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey,
-					privateKey, created);
+					privateKey, created, status);
 			if (rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
@@ -1374,7 +1378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, privateKey, created"
+			String sql = "SELECT authorId, name, publicKey, privateKey,"
+					+ " created, status"
 					+ " FROM localAuthors";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
@@ -1385,8 +1390,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 				byte[] publicKey = rs.getBytes(3);
 				byte[] privateKey = rs.getBytes(4);
 				long created = rs.getLong(5);
+				LocalAuthor.Status status = LocalAuthor.Status.fromValue(
+						rs.getInt(6));
 				authors.add(new LocalAuthor(authorId, name, publicKey,
-						privateKey, created));
+						privateKey, created, status));
 			}
 			rs.close();
 			ps.close();
@@ -2398,7 +2405,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void mergeSettings(Connection txn, Settings s, String namespace) throws DbException {
+	public void mergeSettings(Connection txn, Settings s, String namespace)
+			throws DbException {
 		PreparedStatement ps = null;
 		try {
 			// Update any settings that already exist
@@ -2712,6 +2720,24 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setLocalAuthorStatus(Connection txn, AuthorId a,
+			LocalAuthor.Status s) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE localAuthors SET status = ?"
+					+ " WHERE authorId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, s.getValue());
+			ps.setBytes(2, a.getBytes());
+			int affected = ps.executeUpdate();
+			if (affected < 0 || affected > 1) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void setMessageValidity(Connection txn, MessageId m, boolean valid)
 		throws DbException {
 		PreparedStatement ps = null;
diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
index 89e4f324d3..2e6350cbe7 100644
--- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
+++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
@@ -4,38 +4,114 @@ import com.google.inject.Inject;
 
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.NoSuchLocalAuthorException;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.LocalAuthorAddedEvent;
+import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.lifecycle.Service;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
 
-class IdentityManagerImpl implements IdentityManager {
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.identity.LocalAuthor.Status.ACTIVE;
+import static org.briarproject.api.identity.LocalAuthor.Status.ADDING;
+import static org.briarproject.api.identity.LocalAuthor.Status.REMOVING;
+
+class IdentityManagerImpl implements IdentityManager, Service {
+
+	private static final Logger LOG =
+			Logger.getLogger(IdentityManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
+	private final EventBus eventBus;
+	private final List<IdentityAddedHook> addHooks;
+	private final List<IdentityRemovedHook> removeHooks;
 
 	@Inject
-	IdentityManagerImpl(DatabaseComponent db) {
+	IdentityManagerImpl(DatabaseComponent db, EventBus eventBus) {
 		this.db = db;
+		this.eventBus = eventBus;
+		addHooks = new CopyOnWriteArrayList<IdentityAddedHook>();
+		removeHooks = new CopyOnWriteArrayList<IdentityRemovedHook>();
+	}
+
+	@Override
+	public boolean start() {
+		// Finish adding/removing any partly added/removed pseudonyms
+		try {
+			for (LocalAuthor a : db.getLocalAuthors()) {
+				if (a.getStatus().equals(ADDING)) {
+					for (IdentityAddedHook hook : addHooks)
+						hook.identityAdded(a.getId());
+					db.setLocalAuthorStatus(a.getId(), ACTIVE);
+					eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
+				} else if (a.getStatus().equals(REMOVING)) {
+					for (IdentityRemovedHook hook : removeHooks)
+						hook.identityRemoved(a.getId());
+					db.removeLocalAuthor(a.getId());
+					eventBus.broadcast(new LocalAuthorRemovedEvent(a.getId()));
+				}
+			}
+			return true;
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return false;
+		}
+	}
+
+	@Override
+	public boolean stop() {
+		return false;
+	}
+
+	@Override
+	public void registerIdentityAddedHook(IdentityAddedHook hook) {
+		addHooks.add(hook);
+	}
+
+	@Override
+	public void registerIdentityRemovedHook(IdentityRemovedHook hook) {
+		removeHooks.add(hook);
 	}
 
 	@Override
 	public void addLocalAuthor(LocalAuthor a) throws DbException {
 		db.addLocalAuthor(a);
+		for (IdentityAddedHook hook : addHooks) hook.identityAdded(a.getId());
+		db.setLocalAuthorStatus(a.getId(), ACTIVE);
+		eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	@Override
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
-		return db.getLocalAuthor(a);
+		LocalAuthor author = db.getLocalAuthor(a);
+		if (author.getStatus().equals(ACTIVE)) return author;
+		throw new NoSuchLocalAuthorException();
 	}
 
 	@Override
 	public Collection<LocalAuthor> getLocalAuthors() throws DbException {
-		return db.getLocalAuthors();
+		Collection<LocalAuthor> authors = db.getLocalAuthors();
+		// Filter out any pseudonyms that are being added or removed
+		List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size());
+		for (LocalAuthor a : authors)
+			if (a.getStatus().equals(ACTIVE)) active.add(a);
+		return Collections.unmodifiableList(active);
 	}
 
 	@Override
 	public void removeLocalAuthor(AuthorId a) throws DbException {
+		db.setLocalAuthorStatus(a, REMOVING);
+		for (IdentityRemovedHook hook : removeHooks) hook.identityRemoved(a);
 		db.removeLocalAuthor(a);
+		eventBus.broadcast(new LocalAuthorRemovedEvent(a));
 	}
 }
diff --git a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
index bbe2674d0a..102ea1ee89 100644
--- a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
@@ -14,6 +14,8 @@ import java.io.IOException;
 
 import javax.inject.Inject;
 
+import static org.briarproject.api.identity.LocalAuthor.Status.ADDING;
+
 class AuthorFactoryImpl implements AuthorFactory {
 
 	private final CryptoComponent crypto;
@@ -35,7 +37,7 @@ class AuthorFactoryImpl implements AuthorFactory {
 	public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
 			byte[] privateKey) {
 		return new LocalAuthor(getId(name, publicKey), name, publicKey,
-				privateKey, clock.currentTimeMillis());
+				privateKey, clock.currentTimeMillis(), ADDING);
 	}
 
 	private AuthorId getId(String name, byte[] publicKey) {
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index b73ac3ee0b..285da1aebf 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -2,7 +2,6 @@ package org.briarproject.db;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.Contact;
@@ -17,8 +16,6 @@ import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.LocalAuthorAddedEvent;
-import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
@@ -57,7 +54,6 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 
-import static org.briarproject.api.contact.Contact.Status.ACTIVE;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
 import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
@@ -97,11 +93,12 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
+		long timestamp = System.currentTimeMillis();
 		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				LocalAuthor.Status.ACTIVE);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
-		long timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		message = new Message(messageId, groupId, timestamp, raw);
@@ -112,7 +109,8 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 				"bar", "baz"));
 		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, localAuthorId, ACTIVE);
+		contact = new Contact(contactId, author, localAuthorId,
+				Contact.Status.ACTIVE);
 	}
 
 	private <T> DatabaseComponent createDatabaseComponent(Database<T> database,
@@ -141,7 +139,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).containsContact(txn, authorId);
 			will(returnValue(false));
@@ -184,10 +181,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			// removeLocalAuthor()
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(true));
-			oneOf(database).getContacts(txn, localAuthorId);
-			will(returnValue(Collections.emptyList()));
 			oneOf(database).removeLocalAuthor(txn, localAuthorId);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
 			// close()
 			oneOf(shutdown).removeShutdownHook(shutdownHandle);
 			oneOf(database).close();
@@ -657,7 +651,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
 			oneOf(database).commitTransaction(txn);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index f034e3f4be..5f13ad4631 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -3,6 +3,7 @@ package org.briarproject.db;
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestDatabaseConfig;
 import org.briarproject.TestUtils;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
@@ -21,7 +22,6 @@ import org.briarproject.api.sync.MessageStatus;
 import org.briarproject.api.transport.IncomingKeys;
 import org.briarproject.api.transport.OutgoingKeys;
 import org.briarproject.api.transport.TransportKeys;
-import org.briarproject.api.Settings;
 import org.briarproject.system.SystemClock;
 import org.junit.After;
 import org.junit.Before;
@@ -80,10 +80,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
+		timestamp = System.currentTimeMillis();
 		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				LocalAuthor.Status.ACTIVE);
 		messageId = new MessageId(TestUtils.getRandomId());
-		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		random.nextBytes(raw);
@@ -94,7 +95,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	@Before
 	public void setUp() {
-		testDir.mkdirs();
+		assertTrue(testDir.mkdirs());
 	}
 
 	@Test
diff --git a/briar-tests/src/org/briarproject/sync/ConstantsTest.java b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
index c2fa6bf5e1..a67afd3614 100644
--- a/briar-tests/src/org/briarproject/sync/ConstantsTest.java
+++ b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
@@ -41,6 +41,7 @@ import org.briarproject.data.DataModule;
 import org.briarproject.db.DatabaseModule;
 import org.briarproject.event.EventModule;
 import org.briarproject.forum.ForumModule;
+import org.briarproject.identity.IdentityModule;
 import org.briarproject.messaging.MessagingModule;
 import org.junit.Test;
 
@@ -64,6 +65,8 @@ import static org.junit.Assert.assertTrue;
 
 public class ConstantsTest extends BriarTestCase {
 
+	// TODO: Break this up into tests that are relevant for each package
+
 	private final CryptoComponent crypto;
 	private final GroupFactory groupFactory;
 	private final AuthorFactory authorFactory;
@@ -76,7 +79,7 @@ public class ConstantsTest extends BriarTestCase {
 				new TestLifecycleModule(), new TestSystemModule(),
 				new ContactModule(), new CryptoModule(), new DatabaseModule(),
 				new DataModule(), new EventModule(), new ForumModule(),
-				new MessagingModule(), new SyncModule());
+				new IdentityModule(), new MessagingModule(), new SyncModule());
 		crypto = i.getInstance(CryptoComponent.class);
 		groupFactory = i.getInstance(GroupFactory.class);
 		authorFactory = i.getInstance(AuthorFactory.class);
diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
index eb0db4854e..dcb7292816 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
@@ -123,7 +123,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.addTransport(transportId, MAX_LATENCY);
 		// Add an identity for Alice
 		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], timestamp);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				LocalAuthor.Status.ADDING);
 		identityManager.addLocalAuthor(aliceAuthor);
 		// Add Bob as a contact
 		Author bobAuthor = new Author(bobId, "Bob",
@@ -188,7 +189,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.addTransport(transportId, MAX_LATENCY);
 		// Add an identity for Bob
 		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], timestamp);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				LocalAuthor.Status.ADDING);
 		identityManager.addLocalAuthor(bobAuthor);
 		// Add Alice as a contact
 		Author aliceAuthor = new Author(aliceId, "Alice",
-- 
GitLab