mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 23:59:54 +01:00
Added the ability to remove pseudonyms from the database.
This commit is contained in:
@@ -56,7 +56,7 @@ public interface DatabaseComponent {
|
|||||||
/** Stores an endpoint. */
|
/** Stores an endpoint. */
|
||||||
void addEndpoint(Endpoint ep) throws DbException;
|
void addEndpoint(Endpoint ep) throws DbException;
|
||||||
|
|
||||||
/** Stores a pseudonym that the user can use to sign messages. */
|
/** Stores a local pseudonym. */
|
||||||
void addLocalAuthor(LocalAuthor a) throws DbException;
|
void addLocalAuthor(LocalAuthor a) throws DbException;
|
||||||
|
|
||||||
/** Stores a locally generated group message. */
|
/** Stores a locally generated group message. */
|
||||||
@@ -179,10 +179,10 @@ public interface DatabaseComponent {
|
|||||||
*/
|
*/
|
||||||
Map<ContactId, Long> getLastConnected() throws DbException;
|
Map<ContactId, Long> getLastConnected() throws DbException;
|
||||||
|
|
||||||
/** Returns the pseudonym with the given ID. */
|
/** Returns the local pseudonym with the given ID. */
|
||||||
LocalAuthor getLocalAuthor(AuthorId a) throws DbException;
|
LocalAuthor getLocalAuthor(AuthorId a) throws DbException;
|
||||||
|
|
||||||
/** Returns all pseudonyms that the user can use to sign messages. */
|
/** Returns all local pseudonyms. */
|
||||||
Collection<LocalAuthor> getLocalAuthors() throws DbException;
|
Collection<LocalAuthor> getLocalAuthors() throws DbException;
|
||||||
|
|
||||||
/** Returns the local transport properties for all transports. */
|
/** Returns the local transport properties for all transports. */
|
||||||
@@ -295,6 +295,11 @@ public interface DatabaseComponent {
|
|||||||
/** Removes a contact (and all associated state) from the database. */
|
/** Removes a contact (and all associated state) from the database. */
|
||||||
void removeContact(ContactId c) throws DbException;
|
void removeContact(ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a local pseudonym (and all associated state) from the database.
|
||||||
|
*/
|
||||||
|
void removeLocalAuthor(AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a transport (and any associated configuration and local
|
* Removes a transport (and any associated configuration and local
|
||||||
* properties) from the database.
|
* properties) from the database.
|
||||||
@@ -302,8 +307,8 @@ public interface DatabaseComponent {
|
|||||||
void removeTransport(TransportId t) throws DbException;
|
void removeTransport(TransportId t) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the connection reordering window for the given endoint in the given
|
* Sets the connection reordering window for the given endpoint in the
|
||||||
* rotation period.
|
* given rotation period.
|
||||||
*/
|
*/
|
||||||
void setConnectionWindow(ContactId c, TransportId t, long period,
|
void setConnectionWindow(ContactId c, TransportId t, long period,
|
||||||
long centre, byte[] bitmap) throws DbException;
|
long centre, byte[] bitmap) throws DbException;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package net.sf.briar.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a duplicate pseudonym is added to the database. This exception
|
||||||
|
* may occur due to concurrent updates and does not indicate a database error.
|
||||||
|
*/
|
||||||
|
public class LocalAuthorExistsException extends DbException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -1483877298070151673L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package net.sf.briar.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a database operation is attempted for a pseudonym that is not in
|
||||||
|
* the database. This exception may occur due to concurrent updates and does
|
||||||
|
* not indicate a database error.
|
||||||
|
*/
|
||||||
|
public class NoSuchLocalAuthorException extends DbException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 494398665376703860L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package net.sf.briar.api.db.event;
|
||||||
|
|
||||||
|
import net.sf.briar.api.AuthorId;
|
||||||
|
|
||||||
|
/** An event that is broadcast when a pseudonym for the user is added. */
|
||||||
|
public class LocalAuthorAddedEvent extends DatabaseEvent {
|
||||||
|
|
||||||
|
private final AuthorId authorId;
|
||||||
|
|
||||||
|
public LocalAuthorAddedEvent(AuthorId authorId) {
|
||||||
|
this.authorId = authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorId getAuthorId() {
|
||||||
|
return authorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package net.sf.briar.api.db.event;
|
||||||
|
|
||||||
|
import net.sf.briar.api.AuthorId;
|
||||||
|
|
||||||
|
/** An event that is broadcast when a pseudonym for the user is removed. */
|
||||||
|
public class LocalAuthorRemovedEvent extends DatabaseEvent {
|
||||||
|
|
||||||
|
private final AuthorId authorId;
|
||||||
|
|
||||||
|
public LocalAuthorRemovedEvent(AuthorId authorId) {
|
||||||
|
this.authorId = authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorId getAuthorId() {
|
||||||
|
return authorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,8 @@ import net.sf.briar.api.transport.TemporarySecret;
|
|||||||
* <li> transport
|
* <li> transport
|
||||||
* <li> window
|
* <li> window
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* If table A has a foreign key pointing to table B, we get a read lock on A to
|
||||||
|
* read A, a write lock on A to write A, and write locks on A and B to write B.
|
||||||
*/
|
*/
|
||||||
interface Database<T> {
|
interface Database<T> {
|
||||||
|
|
||||||
@@ -80,8 +82,8 @@ interface Database<T> {
|
|||||||
* Stores a contact with the given pseudonym, associated with the given
|
* Stores a contact with the given pseudonym, associated with the given
|
||||||
* local pseudonym, and returns an ID for the contact.
|
* local pseudonym, and returns an ID for the contact.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: contact write, retention write, subscription write, transport
|
* Locking: contact write, message write, retention write,
|
||||||
* write, window write.
|
* subscription write, transport write, window write.
|
||||||
*/
|
*/
|
||||||
ContactId addContact(T txn, Author remote, AuthorId local)
|
ContactId addContact(T txn, Author remote, AuthorId local)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
@@ -103,9 +105,10 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a pseudonym that the user can use to sign messages.
|
* Stores a local pseudonym.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: contact write, identity write.
|
* Locking: contact write, identity write, message write, retention write,
|
||||||
|
* subscription write, transport write, window write.
|
||||||
*/
|
*/
|
||||||
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
|
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
|
||||||
|
|
||||||
@@ -181,6 +184,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
boolean containsContact(T txn, ContactId c) throws DbException;
|
boolean containsContact(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the database contains the given local pseudonym.
|
||||||
|
* <p>
|
||||||
|
* Locking: identity read.
|
||||||
|
*/
|
||||||
|
boolean containsLocalAuthor(T txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given message.
|
* Returns true if the database contains the given message.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -246,6 +256,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Collection<Contact> getContacts(T txn) throws DbException;
|
Collection<Contact> getContacts(T txn) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all contacts associated with the given local pseudonym.
|
||||||
|
* <p>
|
||||||
|
* Locking: contact read.
|
||||||
|
*/
|
||||||
|
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all endpoints.
|
* Returns all endpoints.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -291,14 +308,14 @@ interface Database<T> {
|
|||||||
Map<ContactId, Long> getLastConnected(T txn) throws DbException;
|
Map<ContactId, Long> getLastConnected(T txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pseudonym with the given ID.
|
* Returns the local pseudonym with the given ID.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: identitiy read.
|
* Locking: identity read.
|
||||||
*/
|
*/
|
||||||
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
|
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all pseudonyms that the user can use to sign messages.
|
* Returns all local pseudonyms.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: identity read.
|
* Locking: identity read.
|
||||||
*/
|
*/
|
||||||
@@ -566,6 +583,15 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removeContact(T txn, ContactId c) throws DbException;
|
void removeContact(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the local pseudonym with the given ID (and all associated
|
||||||
|
* state) from the database.
|
||||||
|
* <p>
|
||||||
|
* Locking: contact write, identity write, message write, retention write,
|
||||||
|
* subscription write, transport write, window write.
|
||||||
|
*/
|
||||||
|
void removeLocalAuthor(T txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a message (and all associated state) from the database.
|
* Removes a message (and all associated state) from the database.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ import net.sf.briar.api.db.ContactExistsException;
|
|||||||
import net.sf.briar.api.db.DatabaseComponent;
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.DbException;
|
import net.sf.briar.api.db.DbException;
|
||||||
import net.sf.briar.api.db.GroupMessageHeader;
|
import net.sf.briar.api.db.GroupMessageHeader;
|
||||||
|
import net.sf.briar.api.db.LocalAuthorExistsException;
|
||||||
import net.sf.briar.api.db.NoSuchContactException;
|
import net.sf.briar.api.db.NoSuchContactException;
|
||||||
|
import net.sf.briar.api.db.NoSuchLocalAuthorException;
|
||||||
import net.sf.briar.api.db.NoSuchMessageException;
|
import net.sf.briar.api.db.NoSuchMessageException;
|
||||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||||
import net.sf.briar.api.db.NoSuchTransportException;
|
import net.sf.briar.api.db.NoSuchTransportException;
|
||||||
@@ -48,6 +50,8 @@ import net.sf.briar.api.db.event.ContactRemovedEvent;
|
|||||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||||
import net.sf.briar.api.db.event.DatabaseListener;
|
import net.sf.briar.api.db.event.DatabaseListener;
|
||||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||||
|
import net.sf.briar.api.db.event.LocalAuthorAddedEvent;
|
||||||
|
import net.sf.briar.api.db.event.LocalAuthorRemovedEvent;
|
||||||
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
||||||
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
|
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
|
||||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||||
@@ -186,35 +190,47 @@ DatabaseCleaner.Callback {
|
|||||||
ContactId c;
|
ContactId c;
|
||||||
contactLock.writeLock().lock();
|
contactLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
retentionLock.writeLock().lock();
|
identityLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
subscriptionLock.writeLock().lock();
|
messageLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
transportLock.writeLock().lock();
|
retentionLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
windowLock.writeLock().lock();
|
subscriptionLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
T txn = db.startTransaction();
|
transportLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
if(db.containsContact(txn, remote.getId()))
|
windowLock.writeLock().lock();
|
||||||
throw new ContactExistsException();
|
try {
|
||||||
c = db.addContact(txn, remote, local);
|
T txn = db.startTransaction();
|
||||||
db.commitTransaction(txn);
|
try {
|
||||||
} catch(DbException e) {
|
if(db.containsContact(txn, remote.getId()))
|
||||||
db.abortTransaction(txn);
|
throw new ContactExistsException();
|
||||||
throw e;
|
if(!db.containsLocalAuthor(txn, local))
|
||||||
|
throw new NoSuchLocalAuthorException();
|
||||||
|
c = db.addContact(txn, remote, local);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
windowLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
transportLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
windowLock.writeLock().unlock();
|
subscriptionLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
transportLock.writeLock().unlock();
|
retentionLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
subscriptionLock.writeLock().unlock();
|
messageLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
retentionLock.writeLock().unlock();
|
identityLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
contactLock.writeLock().unlock();
|
contactLock.writeLock().unlock();
|
||||||
@@ -263,13 +279,40 @@ DatabaseCleaner.Callback {
|
|||||||
try {
|
try {
|
||||||
identityLock.writeLock().lock();
|
identityLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
T txn = db.startTransaction();
|
messageLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
db.addLocalAuthor(txn, a);
|
retentionLock.writeLock().lock();
|
||||||
db.commitTransaction(txn);
|
try {
|
||||||
} catch(DbException e) {
|
subscriptionLock.writeLock().lock();
|
||||||
db.abortTransaction(txn);
|
try {
|
||||||
throw e;
|
transportLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
windowLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
T txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
if(db.containsLocalAuthor(txn, a.getId()))
|
||||||
|
throw new LocalAuthorExistsException();
|
||||||
|
db.addLocalAuthor(txn, a);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
windowLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
transportLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
subscriptionLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
retentionLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
messageLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
identityLock.writeLock().unlock();
|
identityLock.writeLock().unlock();
|
||||||
@@ -277,6 +320,7 @@ DatabaseCleaner.Callback {
|
|||||||
} finally {
|
} finally {
|
||||||
contactLock.writeLock().unlock();
|
contactLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
callListeners(new LocalAuthorAddedEvent(a.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLocalGroupMessage(Message m) throws DbException {
|
public void addLocalGroupMessage(Message m) throws DbException {
|
||||||
@@ -942,6 +986,8 @@ DatabaseCleaner.Callback {
|
|||||||
try {
|
try {
|
||||||
T txn = db.startTransaction();
|
T txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
|
if(!db.containsLocalAuthor(txn, a))
|
||||||
|
throw new NoSuchLocalAuthorException();
|
||||||
LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
|
LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
return localAuthor;
|
return localAuthor;
|
||||||
@@ -1643,6 +1689,58 @@ DatabaseCleaner.Callback {
|
|||||||
callListeners(new ContactRemovedEvent(c));
|
callListeners(new ContactRemovedEvent(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeLocalAuthor(AuthorId a) throws DbException {
|
||||||
|
Collection<ContactId> affected;
|
||||||
|
contactLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
identityLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
messageLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
retentionLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
subscriptionLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
transportLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
windowLock.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) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
windowLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
transportLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
subscriptionLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
retentionLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
messageLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
identityLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
contactLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
for(ContactId c : affected) callListeners(new ContactRemovedEvent(c));
|
||||||
|
callListeners(new LocalAuthorRemovedEvent(a));
|
||||||
|
}
|
||||||
|
|
||||||
public void removeTransport(TransportId t) throws DbException {
|
public void removeTransport(TransportId t) throws DbException {
|
||||||
transportLock.writeLock().lock();
|
transportLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import net.sf.briar.api.transport.TemporarySecret;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Locking: identity
|
// Locking: identity
|
||||||
|
// Dependents: contact, message, retention, subscription, transport, window
|
||||||
private static final String CREATE_LOCAL_AUTHORS =
|
private static final String CREATE_LOCAL_AUTHORS =
|
||||||
"CREATE TABLE localAuthors"
|
"CREATE TABLE localAuthors"
|
||||||
+ " (authorId HASH NOT NULL,"
|
+ " (authorId HASH NOT NULL,"
|
||||||
@@ -81,10 +82,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " UNIQUE (authorId),"
|
+ " UNIQUE (authorId),"
|
||||||
+ " FOREIGN KEY (localAuthorId)"
|
+ " FOREIGN KEY (localAuthorId)"
|
||||||
+ " REFERENCES localAuthors (authorId)"
|
+ " REFERENCES localAuthors (authorId)"
|
||||||
+ " ON DELETE RESTRICT)"; // Deletion not allowed
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String INDEX_CONTACTS_BY_AUTHOR =
|
|
||||||
"CREATE INDEX contactsByAuthor ON contacts (authorId)";
|
|
||||||
|
|
||||||
// Locking: subscription
|
// Locking: subscription
|
||||||
// Dependents: message
|
// Dependents: message
|
||||||
@@ -376,7 +374,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
|
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
||||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
|
|
||||||
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
|
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
|
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
|
||||||
@@ -1000,6 +997,27 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean containsLocalAuthor(Connection txn, AuthorId a)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT NULL FROM localAuthors WHERE authorId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, a.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
if(rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return found;
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean containsMessage(Connection txn, MessageId m)
|
public boolean containsMessage(Connection txn, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -1228,6 +1246,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<ContactId> getContacts(Connection txn, AuthorId a)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT contactId FROM contacts"
|
||||||
|
+ " WHERE localAuthorId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, a.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
List<ContactId> ids = new ArrayList<ContactId>();
|
||||||
|
while(rs.next()) ids.add(new ContactId(rs.getInt(1)));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return Collections.unmodifiableList(ids);
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<Endpoint> getEndpoints(Connection txn)
|
public Collection<Endpoint> getEndpoints(Connection txn)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -2543,6 +2583,22 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeLocalAuthor(Connection txn, AuthorId a)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "DELETE FROM localAuthors WHERE authorId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, a.getBytes());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void removeMessage(Connection txn, MessageId m) throws DbException {
|
public void removeMessage(Connection txn, MessageId m) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ import net.sf.briar.api.TransportProperties;
|
|||||||
import net.sf.briar.api.db.AckAndRequest;
|
import net.sf.briar.api.db.AckAndRequest;
|
||||||
import net.sf.briar.api.db.DatabaseComponent;
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.NoSuchContactException;
|
import net.sf.briar.api.db.NoSuchContactException;
|
||||||
|
import net.sf.briar.api.db.NoSuchLocalAuthorException;
|
||||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||||
import net.sf.briar.api.db.NoSuchTransportException;
|
import net.sf.briar.api.db.NoSuchTransportException;
|
||||||
import net.sf.briar.api.db.event.ContactAddedEvent;
|
import net.sf.briar.api.db.event.ContactAddedEvent;
|
||||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||||
import net.sf.briar.api.db.event.DatabaseListener;
|
import net.sf.briar.api.db.event.DatabaseListener;
|
||||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||||
|
import net.sf.briar.api.db.event.LocalAuthorAddedEvent;
|
||||||
|
import net.sf.briar.api.db.event.LocalAuthorRemovedEvent;
|
||||||
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
||||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||||
import net.sf.briar.api.db.event.SubscriptionAddedEvent;
|
import net.sf.briar.api.db.event.SubscriptionAddedEvent;
|
||||||
@@ -122,9 +125,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
exactly(10).of(database).startTransaction();
|
exactly(11).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(10).of(database).commitTransaction(txn);
|
exactly(11).of(database).commitTransaction(txn);
|
||||||
// open()
|
// open()
|
||||||
oneOf(database).open();
|
oneOf(database).open();
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
@@ -134,10 +137,16 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||||
will(returnValue(shutdownHandle));
|
will(returnValue(shutdownHandle));
|
||||||
// addLocalAuthor(localAuthor)
|
// addLocalAuthor(localAuthor)
|
||||||
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(false));
|
||||||
oneOf(database).addLocalAuthor(txn, localAuthor);
|
oneOf(database).addLocalAuthor(txn, localAuthor);
|
||||||
|
oneOf(listener).eventOccurred(with(any(
|
||||||
|
LocalAuthorAddedEvent.class)));
|
||||||
// addContact(author, localAuthorId)
|
// addContact(author, localAuthorId)
|
||||||
oneOf(database).containsContact(txn, authorId);
|
oneOf(database).containsContact(txn, authorId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(true));
|
||||||
oneOf(database).addContact(txn, author, localAuthorId);
|
oneOf(database).addContact(txn, author, localAuthorId);
|
||||||
will(returnValue(contactId));
|
will(returnValue(contactId));
|
||||||
oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class)));
|
oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class)));
|
||||||
@@ -180,6 +189,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).removeContact(txn, contactId);
|
oneOf(database).removeContact(txn, contactId);
|
||||||
oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class)));
|
oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class)));
|
||||||
|
// removeLocalAuthor(localAuthorId)
|
||||||
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).getContacts(txn, localAuthorId);
|
||||||
|
will(returnValue(Collections.emptyList()));
|
||||||
|
oneOf(database).removeLocalAuthor(txn, localAuthorId);
|
||||||
|
oneOf(listener).eventOccurred(with(any(
|
||||||
|
LocalAuthorRemovedEvent.class)));
|
||||||
// close()
|
// close()
|
||||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
||||||
oneOf(cleaner).stopCleaning();
|
oneOf(cleaner).stopCleaning();
|
||||||
@@ -202,6 +219,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
assertEquals(Arrays.asList(groupId), db.getSubscriptions());
|
assertEquals(Arrays.asList(groupId), db.getSubscriptions());
|
||||||
db.unsubscribe(group);
|
db.unsubscribe(group);
|
||||||
db.removeContact(contactId);
|
db.removeContact(contactId);
|
||||||
|
db.removeLocalAuthor(localAuthorId);
|
||||||
db.removeListener(listener);
|
db.removeListener(listener);
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
@@ -512,6 +530,46 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
||||||
|
throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Database<Object> database = context.mock(Database.class);
|
||||||
|
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||||
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// Check whether the pseudonym is in the DB (which it's not)
|
||||||
|
exactly(3).of(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
exactly(3).of(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(false));
|
||||||
|
exactly(3).of(database).abortTransaction(txn);
|
||||||
|
// This is needed for addContact() to proceed
|
||||||
|
exactly(1).of(database).containsContact(txn, authorId);
|
||||||
|
will(returnValue(false));
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
|
shutdown);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.addContact(author, localAuthorId);
|
||||||
|
fail();
|
||||||
|
} catch(NoSuchLocalAuthorException expected) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.getLocalAuthor(localAuthorId);
|
||||||
|
fail();
|
||||||
|
} catch(NoSuchLocalAuthorException expected) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.removeLocalAuthor(localAuthorId);
|
||||||
|
fail();
|
||||||
|
} catch(NoSuchLocalAuthorException expected) {}
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing()
|
public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -571,6 +629,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
// addLocalAuthor(localAuthor)
|
// addLocalAuthor(localAuthor)
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(false));
|
||||||
oneOf(database).addLocalAuthor(txn, localAuthor);
|
oneOf(database).addLocalAuthor(txn, localAuthor);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// addContact(author, localAuthorId)
|
// addContact(author, localAuthorId)
|
||||||
@@ -578,6 +638,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(database).containsContact(txn, authorId);
|
oneOf(database).containsContact(txn, authorId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||||
|
will(returnValue(true));
|
||||||
oneOf(database).addContact(txn, author, localAuthorId);
|
oneOf(database).addContact(txn, author, localAuthorId);
|
||||||
will(returnValue(contactId));
|
will(returnValue(contactId));
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
|
|||||||
@@ -1715,6 +1715,31 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetContactsByLocalAuthorId() throws Exception {
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a local author - no contacts should be associated
|
||||||
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
|
Collection<ContactId> contacts = db.getContacts(txn, localAuthorId);
|
||||||
|
assertEquals(Collections.emptyList(), contacts);
|
||||||
|
|
||||||
|
// Add a contact associated with the local author
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
|
contacts = db.getContacts(txn, localAuthorId);
|
||||||
|
assertEquals(Collections.singletonList(contactId), contacts);
|
||||||
|
|
||||||
|
// Remove the local author - the contact should be removed
|
||||||
|
db.removeLocalAuthor(txn, localAuthorId);
|
||||||
|
contacts = db.getContacts(txn, localAuthorId);
|
||||||
|
assertEquals(Collections.emptyList(), contacts);
|
||||||
|
assertFalse(db.containsContact(txn, contactId));
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExceptionHandling() throws Exception {
|
public void testExceptionHandling() throws Exception {
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user