mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Use a separate linked list for each contact, containing visible groups.
This commit is contained in:
@@ -132,6 +132,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
TransportIndex addTransport(T txn, TransportId t) throws DbException;
|
TransportIndex addTransport(T txn, TransportId t) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the given group visible to the given contact.
|
||||||
|
* <p>
|
||||||
|
* Locking: contact read, subscription write.
|
||||||
|
*/
|
||||||
|
void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact.
|
* Returns true if the database contains the given contact.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -514,6 +521,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removeSubscription(T txn, GroupId g) throws DbException;
|
void removeSubscription(T txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the given group invisible to the given contact.
|
||||||
|
* <p>
|
||||||
|
* Locking: contact read, subscription write.
|
||||||
|
*/
|
||||||
|
void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the configuration for the given transport, replacing any existing
|
* Sets the configuration for the given transport, replacing any existing
|
||||||
* configuration for that transport.
|
* configuration for that transport.
|
||||||
@@ -642,13 +656,4 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void setTransportsSent(T txn, ContactId c, long timestamp)
|
void setTransportsSent(T txn, ContactId c, long timestamp)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the given group visible to the given set of contacts and invisible
|
|
||||||
* to any other contacts.
|
|
||||||
* <p>
|
|
||||||
* Locking: contact read, subscription write.
|
|
||||||
*/
|
|
||||||
void setVisibility(T txn, GroupId g, Collection<ContactId> visible)
|
|
||||||
throws DbException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1425,7 +1425,7 @@ DatabaseCleaner.Callback {
|
|||||||
|
|
||||||
public void setVisibility(GroupId g, Collection<ContactId> visible)
|
public void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
List<ContactId> affected;
|
List<ContactId> affected = new ArrayList<ContactId>();
|
||||||
contactLock.readLock().lock();
|
contactLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
subscriptionLock.writeLock().lock();
|
subscriptionLock.writeLock().lock();
|
||||||
@@ -1433,23 +1433,25 @@ DatabaseCleaner.Callback {
|
|||||||
T txn = db.startTransaction();
|
T txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
// Use HashSets for O(1) lookups, O(n) overall running time
|
// Use HashSets for O(1) lookups, O(n) overall running time
|
||||||
HashSet<ContactId> then, now;
|
visible = new HashSet<ContactId>(visible);
|
||||||
// Retrieve the group's current visibility
|
HashSet<ContactId> oldVisible =
|
||||||
then = new HashSet<ContactId>(db.getVisibility(txn, g));
|
new HashSet<ContactId>(db.getVisibility(txn, g));
|
||||||
// Don't try to make the group visible to ex-contacts
|
// Set the group's visibility for each current contact
|
||||||
now = new HashSet<ContactId>(visible);
|
for(ContactId c : db.getContacts(txn)) {
|
||||||
now.retainAll(new HashSet<ContactId>(db.getContacts(txn)));
|
boolean then = oldVisible.contains(c);
|
||||||
db.setVisibility(txn, g, now);
|
boolean now = visible.contains(c);
|
||||||
// Work out which contacts were affected by the change
|
if(!then && now) {
|
||||||
affected = new ArrayList<ContactId>();
|
db.addVisibility(txn, c, g);
|
||||||
for(ContactId c : then) {
|
affected.add(c);
|
||||||
if(!now.contains(c)) affected.add(c);
|
} else if(then && !now) {
|
||||||
|
db.removeVisibility(txn, c, g);
|
||||||
|
affected.add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for(ContactId c : now) {
|
if(!affected.isEmpty()) {
|
||||||
if(!then.contains(c)) affected.add(c);
|
db.setSubscriptionsModified(txn, affected,
|
||||||
|
System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
db.setSubscriptionsModified(txn, affected,
|
|
||||||
System.currentTimeMillis());
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
db.abortTransaction(txn);
|
db.abortTransaction(txn);
|
||||||
|
|||||||
@@ -59,12 +59,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " start BIGINT NOT NULL,"
|
+ " start BIGINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId))";
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
private static final String CREATE_SUBSCRIPTION_IDS =
|
|
||||||
"CREATE TABLE subscriptionIds"
|
|
||||||
+ " (groupId HASH," // Null for the head of the list
|
|
||||||
+ " nextId HASH," // Null for the tail of the list
|
|
||||||
+ " deleted BIGINT NOT NULL)";
|
|
||||||
|
|
||||||
private static final String CREATE_CONTACTS =
|
private static final String CREATE_CONTACTS =
|
||||||
"CREATE TABLE contacts"
|
"CREATE TABLE contacts"
|
||||||
+ " (contactId COUNTER,"
|
+ " (contactId COUNTER,"
|
||||||
@@ -104,12 +98,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_VISIBILITIES =
|
private static final String CREATE_VISIBILITIES =
|
||||||
"CREATE TABLE visibilities"
|
"CREATE TABLE visibilities"
|
||||||
+ " (groupId HASH NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " contactId INT NOT NULL,"
|
+ " groupId HASH," // Null for the head of the linked list
|
||||||
+ " PRIMARY KEY (groupId, contactId),"
|
+ " nextId HASH," // Null for the tail of the linked list
|
||||||
+ " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)"
|
+ " deleted BIGINT NOT NULL,"
|
||||||
+ " ON DELETE CASCADE,"
|
|
||||||
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
||||||
|
+ " ON DELETE CASCADE,"
|
||||||
|
+ " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String INDEX_VISIBILITIES_BY_GROUP =
|
private static final String INDEX_VISIBILITIES_BY_GROUP =
|
||||||
@@ -333,7 +328,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTIONS));
|
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTIONS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_IDS));
|
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
|
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
|
s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
|
||||||
@@ -729,7 +723,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
public void addSubscription(Connection txn, Group g) throws DbException {
|
public void addSubscription(Connection txn, Group g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
try {
|
||||||
// Add the group to the subscriptions table
|
// Add the group to the subscriptions table
|
||||||
String sql = "INSERT INTO subscriptions"
|
String sql = "INSERT INTO subscriptions"
|
||||||
@@ -743,78 +736,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if(affected != 1) throw new DbStateException();
|
if(affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Insert the group ID into the linked list
|
|
||||||
byte[] id = g.getId().getBytes();
|
|
||||||
sql = "SELECT groupId, nextId, deleted FROM subscriptionIds"
|
|
||||||
+ " ORDER BY groupId";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
if(rs.next()) {
|
|
||||||
// The head pointer of the list exists
|
|
||||||
byte[] groupId = rs.getBytes(1);
|
|
||||||
if(groupId != null) throw new DbStateException();
|
|
||||||
byte[] nextId = rs.getBytes(2);
|
|
||||||
long deleted = rs.getLong(3);
|
|
||||||
// Scan through the list to find the insertion point
|
|
||||||
while(nextId != null && ByteUtils.compare(id, nextId) > 0) {
|
|
||||||
if(!rs.next()) throw new DbStateException();
|
|
||||||
groupId = rs.getBytes(1);
|
|
||||||
if(groupId == null) throw new DbStateException();
|
|
||||||
nextId = rs.getBytes(2);
|
|
||||||
deleted = rs.getLong(3);
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
// Update the previous element
|
|
||||||
if(groupId == null) {
|
|
||||||
// Inserting at the head of the list
|
|
||||||
sql = "UPDATE subscriptionIds SET nextId = ?"
|
|
||||||
+ " WHERE groupId IS NULL";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
} else {
|
|
||||||
// Inserting in the middle or at the tail of the list
|
|
||||||
sql = "UPDATE subscriptionIds SET nextId = ?"
|
|
||||||
+ " WHERE groupId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ps.setBytes(2, groupId);
|
|
||||||
}
|
|
||||||
affected = ps.executeUpdate();
|
|
||||||
if(affected != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
// Insert the new element
|
|
||||||
sql = "INSERT INTO subscriptionIds (groupId, nextId, deleted)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
if(nextId == null) ps.setNull(2, Types.BINARY); // At the tail
|
|
||||||
else ps.setBytes(2, nextId); // In the middle
|
|
||||||
ps.setLong(3, deleted);
|
|
||||||
affected = ps.executeUpdate();
|
|
||||||
if(affected != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
} else {
|
|
||||||
// The head pointer of the list does not exist
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
sql = "INSERT INTO subscriptionIds (nextId, deleted)"
|
|
||||||
+ " VALUES (?, ZERO())";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
affected = ps.executeUpdate();
|
|
||||||
if(affected != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
sql = "INSERT INTO subscriptionIds (groupId, deleted)"
|
|
||||||
+ " VALUES (?, ZERO())";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
affected = ps.executeUpdate();
|
|
||||||
if(affected != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
}
|
|
||||||
} catch(SQLException e) {
|
} catch(SQLException e) {
|
||||||
tryToClose(rs);
|
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -858,6 +780,96 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addVisibility(Connection txn, ContactId c, GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
// Insert the group ID into the linked list
|
||||||
|
byte[] id = g.getBytes();
|
||||||
|
String sql = "SELECT groupId, nextId, deleted FROM visibilities"
|
||||||
|
+ " WHERE contactId = ?"
|
||||||
|
+ " ORDER BY groupId";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if(rs.next()) {
|
||||||
|
// The head pointer of the list exists
|
||||||
|
byte[] groupId = rs.getBytes(1);
|
||||||
|
if(groupId != null) throw new DbStateException();
|
||||||
|
byte[] nextId = rs.getBytes(2);
|
||||||
|
long deleted = rs.getLong(3);
|
||||||
|
// Scan through the list to find the insertion point
|
||||||
|
while(nextId != null && ByteUtils.compare(id, nextId) > 0) {
|
||||||
|
if(!rs.next()) throw new DbStateException();
|
||||||
|
groupId = rs.getBytes(1);
|
||||||
|
if(groupId == null) throw new DbStateException();
|
||||||
|
nextId = rs.getBytes(2);
|
||||||
|
deleted = rs.getLong(3);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
// Update the previous element
|
||||||
|
if(groupId == null) {
|
||||||
|
// Inserting at the head of the list
|
||||||
|
sql = "UPDATE visibilities SET nextId = ?"
|
||||||
|
+ " WHERE contactId = ? AND groupId IS NULL";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ps.setInt(2, c.getInt());
|
||||||
|
} else {
|
||||||
|
// Inserting in the middle or at the tail of the list
|
||||||
|
sql = "UPDATE visibilities SET nextId = ?"
|
||||||
|
+ " WHERE contactId = ? AND groupId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ps.setInt(2, c.getInt());
|
||||||
|
ps.setBytes(3, groupId);
|
||||||
|
}
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
// Insert the new element
|
||||||
|
sql = "INSERT INTO visibilities"
|
||||||
|
+ " (contactId, groupId, nextId, deleted)"
|
||||||
|
+ " VALUES (?, ?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setBytes(2, id);
|
||||||
|
if(nextId == null) ps.setNull(3, Types.BINARY); // At the tail
|
||||||
|
else ps.setBytes(3, nextId); // In the middle
|
||||||
|
ps.setLong(4, deleted);
|
||||||
|
affected = ps.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} else {
|
||||||
|
// The head pointer of the list does not exist
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
sql = "INSERT INTO visibilities (contactId, nextId, deleted)"
|
||||||
|
+ " VALUES (?, ?, ZERO())";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setBytes(2, id);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
sql = "INSERT INTO visibilities (contactId, groupId, deleted)"
|
||||||
|
+ " VALUES (?, ?, ZERO())";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setBytes(2, id);
|
||||||
|
affected = ps.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean containsContact(Connection txn, ContactId c)
|
public boolean containsContact(Connection txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -2176,39 +2188,82 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
public void removeSubscription(Connection txn, GroupId g)
|
public void removeSubscription(Connection txn, GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null, ps1 = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
|
// Remove the group ID from the visibility lists
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
String sql = "SELECT contactId, nextId FROM visibilities"
|
||||||
|
+ " WHERE groupId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, g.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
while(rs.next()) {
|
||||||
|
int contactId = rs.getInt(1);
|
||||||
|
byte[] nextId = rs.getBytes(2);
|
||||||
|
sql = "UPDATE visibilities SET nextId = ?, deleted = ?"
|
||||||
|
+ " WHERE contactId = ? AND nextId = ?";
|
||||||
|
ps1 = txn.prepareStatement(sql);
|
||||||
|
if(nextId == null) ps1.setNull(1, Types.BINARY); // At the tail
|
||||||
|
else ps1.setBytes(1, nextId); // At the head or in the middle
|
||||||
|
ps1.setLong(2, now);
|
||||||
|
ps1.setInt(3, contactId);
|
||||||
|
ps1.setBytes(4, g.getBytes());
|
||||||
|
int affected = ps1.executeUpdate();
|
||||||
|
if(affected != 1) throw new DbStateException();
|
||||||
|
ps1.close();
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
// Remove the group from the subscriptions table
|
// Remove the group from the subscriptions table
|
||||||
String sql = "DELETE FROM subscriptions WHERE groupId = ?";
|
sql = "DELETE FROM subscriptions WHERE groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if(affected != 1) throw new DbStateException();
|
if(affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
} catch(SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
tryToClose(ps1);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeVisibility(Connection txn, ContactId c, GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
// Remove the group ID from the linked list
|
// Remove the group ID from the linked list
|
||||||
sql = "SELECT nextId FROM subscriptionIds WHERE groupId = ?";
|
String sql = "SELECT nextId FROM visibilities"
|
||||||
|
+ " WHERE contactId = ? AND groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setBytes(2, g.getBytes());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
if(!rs.next()) throw new DbStateException();
|
if(!rs.next()) throw new DbStateException();
|
||||||
byte[] nextId = rs.getBytes(1);
|
byte[] nextId = rs.getBytes(1);
|
||||||
if(rs.next()) throw new DbStateException();
|
if(rs.next()) throw new DbStateException();
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
sql = "DELETE FROM subscriptionIds WHERE groupId = ?";
|
sql = "DELETE FROM visibilities"
|
||||||
|
+ " WHERE contactId = ? AND groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setInt(1, c.getInt());
|
||||||
affected = ps.executeUpdate();
|
ps.setBytes(2, g.getBytes());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
if(affected != 1) throw new DbStateException();
|
if(affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
sql = "UPDATE subscriptionIds SET nextId = ?, deleted = ?"
|
sql = "UPDATE visibilities SET nextId = ?, deleted = ?"
|
||||||
+ " WHERE nextId = ?";
|
+ " WHERE contactId = ? AND nextId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
if(nextId == null) ps.setNull(1, Types.BINARY); // At the tail
|
if(nextId == null) ps.setNull(1, Types.BINARY); // At the tail
|
||||||
else ps.setBytes(1, nextId); // At the head or in the middle
|
else ps.setBytes(1, nextId); // At the head or in the middle
|
||||||
ps.setLong(2, System.currentTimeMillis());
|
ps.setLong(2, System.currentTimeMillis());
|
||||||
ps.setBytes(3, g.getBytes());
|
ps.setInt(3, c.getInt());
|
||||||
|
ps.setBytes(4, g.getBytes());
|
||||||
affected = ps.executeUpdate();
|
affected = ps.executeUpdate();
|
||||||
if(affected != 1) throw new DbStateException();
|
if(affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2800,36 +2855,4 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVisibility(Connection txn, GroupId g,
|
|
||||||
Collection<ContactId> visible) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
try {
|
|
||||||
// Delete any existing visibilities
|
|
||||||
String sql = "DELETE FROM visibilities where groupId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, g.getBytes());
|
|
||||||
ps.executeUpdate();
|
|
||||||
ps.close();
|
|
||||||
// Store the new visibilities
|
|
||||||
sql = "INSERT INTO visibilities (groupId, contactId)"
|
|
||||||
+ " VALUES (?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, g.getBytes());
|
|
||||||
for(ContactId c : visible) {
|
|
||||||
ps.setInt(2, c.getInt());
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
if(batchAffected.length != visible.size())
|
|
||||||
throw new DbStateException();
|
|
||||||
for(int i = 0; i < batchAffected.length; i++) {
|
|
||||||
if(batchAffected[i] != 1) throw new DbStateException();
|
|
||||||
}
|
|
||||||
ps.close();
|
|
||||||
} catch(SQLException e) {
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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.MessagesAddedEvent;
|
import net.sf.briar.api.db.event.MessagesAddedEvent;
|
||||||
import net.sf.briar.api.db.event.RatingChangedEvent;
|
import net.sf.briar.api.db.event.RatingChangedEvent;
|
||||||
|
import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent;
|
||||||
import net.sf.briar.api.db.event.TransportAddedEvent;
|
import net.sf.briar.api.db.event.TransportAddedEvent;
|
||||||
import net.sf.briar.api.lifecycle.ShutdownManager;
|
import net.sf.briar.api.lifecycle.ShutdownManager;
|
||||||
import net.sf.briar.api.protocol.Ack;
|
import net.sf.briar.api.protocol.Ack;
|
||||||
@@ -679,14 +680,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
public void testGenerateBatch() throws Exception {
|
public void testGenerateBatch() throws Exception {
|
||||||
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||||
final byte[] raw1 = new byte[size];
|
final byte[] raw1 = new byte[size];
|
||||||
final Collection<MessageId> sendable = Arrays.asList(new MessageId[] {
|
final Collection<MessageId> sendable =
|
||||||
messageId,
|
Arrays.asList(new MessageId[] {messageId, messageId1});
|
||||||
messageId1
|
final Collection<byte[]> messages =
|
||||||
});
|
Arrays.asList(new byte[][] {raw, raw1});
|
||||||
final Collection<byte[]> messages = Arrays.asList(new byte[][] {
|
|
||||||
raw,
|
|
||||||
raw1
|
|
||||||
});
|
|
||||||
Mockery context = new Mockery();
|
Mockery context = new Mockery();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final Database<Object> database = context.mock(Database.class);
|
final Database<Object> database = context.mock(Database.class);
|
||||||
@@ -733,9 +730,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
requested.add(messageId);
|
requested.add(messageId);
|
||||||
requested.add(messageId1);
|
requested.add(messageId1);
|
||||||
requested.add(messageId2);
|
requested.add(messageId2);
|
||||||
final Collection<byte[]> msgs = Arrays.asList(new byte[][] {
|
final Collection<byte[]> msgs = Arrays.asList(new byte[][] {raw1});
|
||||||
raw1
|
|
||||||
});
|
|
||||||
Mockery context = new Mockery();
|
Mockery context = new Mockery();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final Database<Object> database = context.mock(Database.class);
|
final Database<Object> database = context.mock(Database.class);
|
||||||
@@ -1541,4 +1536,70 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVisibilityChangedCallsListeners() throws Exception {
|
||||||
|
final ContactId contactId1 = new ContactId(234);
|
||||||
|
final Collection<ContactId> both =
|
||||||
|
Arrays.asList(new ContactId[] {contactId, contactId1});
|
||||||
|
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);
|
||||||
|
final PacketFactory packetFactory = context.mock(PacketFactory.class);
|
||||||
|
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).getVisibility(txn, groupId);
|
||||||
|
will(returnValue(both));
|
||||||
|
oneOf(database).getContacts(txn);
|
||||||
|
will(returnValue(both));
|
||||||
|
oneOf(database).removeVisibility(txn, contactId1, groupId);
|
||||||
|
oneOf(database).setSubscriptionsModified(with(txn),
|
||||||
|
with(Collections.singletonList(contactId1)),
|
||||||
|
with(any(long.class)));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
oneOf(listener).eventOccurred(with(any(
|
||||||
|
SubscriptionsUpdatedEvent.class)));
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
|
shutdown, packetFactory);
|
||||||
|
|
||||||
|
db.addListener(listener);
|
||||||
|
db.setVisibility(groupId, Collections.singletonList(contactId));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVisibilityUnchangedDoesNotCallListeners() throws Exception {
|
||||||
|
final ContactId contactId1 = new ContactId(234);
|
||||||
|
final Collection<ContactId> both =
|
||||||
|
Arrays.asList(new ContactId[] {contactId, contactId1});
|
||||||
|
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);
|
||||||
|
final PacketFactory packetFactory = context.mock(PacketFactory.class);
|
||||||
|
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).getVisibility(txn, groupId);
|
||||||
|
will(returnValue(both));
|
||||||
|
oneOf(database).getContacts(txn);
|
||||||
|
will(returnValue(both));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
|
shutdown, packetFactory);
|
||||||
|
|
||||||
|
db.addListener(listener);
|
||||||
|
db.setVisibility(groupId, both);
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setStatus(txn, contactId, messageId, Status.NEW);
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
@@ -393,7 +393,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
@@ -435,7 +435,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
db.setStatus(txn, contactId, messageId, Status.NEW);
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
@@ -474,7 +474,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
db.setStatus(txn, contactId, messageId, Status.NEW);
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
@@ -509,7 +509,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
@@ -553,7 +553,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertFalse(it.hasNext());
|
assertFalse(it.hasNext());
|
||||||
|
|
||||||
// Making the subscription visible should make the message sendable
|
// Making the subscription visible should make the message sendable
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
assertTrue(it.hasNext());
|
assertTrue(it.hasNext());
|
||||||
@@ -674,7 +674,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
@@ -713,7 +713,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
db.setSendability(txn, messageId, 1);
|
db.setSendability(txn, messageId, 1);
|
||||||
@@ -1274,7 +1274,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// the message is older than the contact's subscription
|
// the message is older than the contact's subscription
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1);
|
Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1);
|
||||||
db.setSubscriptions(txn, contactId, subs, 1);
|
db.setSubscriptions(txn, contactId, subs, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
@@ -1298,7 +1298,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
|
|
||||||
@@ -1323,7 +1323,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact and subscribe to a group
|
// Add a contact and subscribe to a group
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
|
|
||||||
// The message is not in the database
|
// The message is not in the database
|
||||||
@@ -1398,7 +1398,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
|
|
||||||
@@ -1420,7 +1420,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact, subscribe to a group and store a message
|
// Add a contact, subscribe to a group and store a message
|
||||||
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
db.addSubscription(txn, group);
|
db.addSubscription(txn, group);
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
db.setSubscriptions(txn, contactId, subscriptions, 1);
|
||||||
db.addGroupMessage(txn, message);
|
db.addGroupMessage(txn, message);
|
||||||
|
|
||||||
@@ -1444,11 +1444,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// The group should not be visible to the contact
|
// The group should not be visible to the contact
|
||||||
assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
|
assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
|
||||||
// Make the group visible to the contact
|
// Make the group visible to the contact
|
||||||
db.setVisibility(txn, groupId, Collections.singletonList(contactId));
|
db.addVisibility(txn, contactId, groupId);
|
||||||
assertEquals(Collections.singletonList(contactId),
|
assertEquals(Collections.singletonList(contactId),
|
||||||
db.getVisibility(txn, groupId));
|
db.getVisibility(txn, groupId));
|
||||||
// Make the group invisible again
|
// Make the group invisible again
|
||||||
db.setVisibility(txn, groupId, Collections.<ContactId>emptyList());
|
db.removeVisibility(txn, contactId, groupId);
|
||||||
assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
|
assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -1895,12 +1895,20 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Add the groups to the database
|
// Subscribe to the groups and add a contact
|
||||||
for(Group g : groups) db.addSubscription(txn, g);
|
for(Group g : groups) db.addSubscription(txn, g);
|
||||||
|
assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
|
||||||
|
|
||||||
// Remove the groups in a different order
|
// Make the groups visible to the contact
|
||||||
Collections.shuffle(groups);
|
Collections.shuffle(groups);
|
||||||
for(Group g : groups) db.removeSubscription(txn, g.getId());
|
for(Group g : groups) db.addVisibility(txn, contactId, g.getId());
|
||||||
|
|
||||||
|
// Make the groups invisible to the contact and remove them
|
||||||
|
Collections.shuffle(groups);
|
||||||
|
for(Group g : groups) {
|
||||||
|
db.removeVisibility(txn, contactId, g.getId());
|
||||||
|
db.removeSubscription(txn, g.getId());
|
||||||
|
}
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user