diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java index 79076e317469c5396f648f46dc67ad1f87fe51d6..4e8095e9a3e8fd0fd920f25c27dec586438123f0 100644 --- a/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java +++ b/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java @@ -234,7 +234,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes()); Author author = item.getAuthor(); if(author != null) { - i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes()); i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName()); i.putExtra("net.sf.briar.RATING", item.getRating().toString()); } diff --git a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java index 416fd13c92f2623fc40bdf04d79f0ad6daeda495..f1cf62ac8f2ee80a044ef7ae11eca466f6fe2a1b 100644 --- a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java +++ b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java @@ -24,7 +24,6 @@ import net.sf.briar.android.BriarService; import net.sf.briar.android.BriarService.BriarServiceConnection; import net.sf.briar.android.widgets.HorizontalBorder; import net.sf.briar.android.widgets.HorizontalSpace; -import net.sf.briar.api.AuthorId; import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.DatabaseUiExecutor; import net.sf.briar.api.db.DatabaseComponent; @@ -66,8 +65,7 @@ implements OnClickListener { private Rating rating = UNRATED; private boolean read; private ImageView thumb = null; - private ImageButton goodButton = null, badButton = null, readButton = null; - private ImageButton prevButton = null, nextButton = null; + private ImageButton readButton = null, prevButton = null, nextButton = null; private ImageButton replyButton = null; private TextView content = null; @@ -75,7 +73,6 @@ implements OnClickListener { @Inject private volatile DatabaseComponent db; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor; private volatile MessageId messageId = null; - private volatile AuthorId authorId = null; @Override public void onCreate(Bundle state) { @@ -92,15 +89,9 @@ implements OnClickListener { b = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID"); if(b == null) throw new IllegalStateException(); messageId = new MessageId(b); - String authorName = null; - b = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID"); - if(b != null) { - authorId = new AuthorId(b); - authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME"); - if(authorName == null) throw new IllegalStateException(); - String r = i.getStringExtra("net.sf.briar.RATING"); - if(r != null) rating = Rating.valueOf(r); - } + String authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME"); + String r = i.getStringExtra("net.sf.briar.RATING"); + if(r != null) rating = Rating.valueOf(r); String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE"); if(contentType == null) throw new IllegalStateException(); long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1); @@ -177,23 +168,6 @@ implements OnClickListener { footer.setOrientation(HORIZONTAL); footer.setGravity(CENTER); - goodButton = new ImageButton(this); - goodButton.setBackgroundResource(0); - goodButton.setImageResource(R.drawable.rating_good); - if(authorName == null) goodButton.setEnabled(false); - else goodButton.setOnClickListener(this); - footer.addView(goodButton); - footer.addView(new HorizontalSpace(this)); - - badButton = new ImageButton(this); - badButton.setBackgroundResource(0); - badButton.setImageResource(R.drawable.rating_bad); - badButton.setOnClickListener(this); - if(authorName == null) badButton.setEnabled(false); - else badButton.setOnClickListener(this); - footer.addView(badButton); - footer.addView(new HorizontalSpace(this)); - readButton = new ImageButton(this); readButton.setBackgroundResource(0); if(read) readButton.setImageResource(R.drawable.content_unread); @@ -309,13 +283,7 @@ implements OnClickListener { } public void onClick(View view) { - if(view == goodButton) { - if(rating == BAD) setRatingInDatabase(UNRATED); - else if(rating == UNRATED) setRatingInDatabase(GOOD); - } else if(view == badButton) { - if(rating == GOOD) setRatingInDatabase(UNRATED); - else if(rating == UNRATED) setRatingInDatabase(BAD); - } else if(view == readButton) { + if(view == readButton) { setReadInDatabase(!read); } else if(view == prevButton) { setResult(RESULT_PREV); @@ -337,38 +305,4 @@ implements OnClickListener { } } } - - private void setRatingInDatabase(final Rating r) { - dbUiExecutor.execute(new Runnable() { - public void run() { - try { - serviceConnection.waitForStartup(); - long now = System.currentTimeMillis(); - db.setRating(authorId, r); - long duration = System.currentTimeMillis() - now; - if(LOG.isLoggable(INFO)) - LOG.info("Setting rating took " + duration + " ms"); - setRatingInUi(r); - } catch(DbException e) { - if(LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } catch(InterruptedException e) { - if(LOG.isLoggable(INFO)) - LOG.info("Interrupted while waiting for service"); - Thread.currentThread().interrupt(); - } - } - }); - } - - private void setRatingInUi(final Rating r) { - runOnUiThread(new Runnable() { - public void run() { - rating = r; - if(r == GOOD) thumb.setImageResource(R.drawable.rating_good); - else if(r == BAD) thumb.setImageResource(R.drawable.rating_bad); - else thumb.setImageResource(R.drawable.rating_unrated); - } - }); - } } diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index 499dd6c933b17569e8be9bcdf0f9b97b371813e5..6d24c05584a6941e7aeb7a185a52eca28a9126b4 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -352,14 +352,6 @@ interface Database<T> { Collection<PrivateMessageHeader> getPrivateMessageHeaders(T txn, ContactId c) throws DbException; - /** - * Returns the IDs of all messages signed by the given author. - * <p> - * Locking: message read. - */ - Collection<MessageId> getMessagesByAuthor(T txn, AuthorId a) - throws DbException; - /** * Returns the IDs of some messages received from the given contact that * need to be acknowledged, up to the given number of messages. @@ -554,6 +546,15 @@ interface Database<T> { */ Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; + /** + * Returns the IDs of all messages posted by the given author to + * unrestricted groups. + * <p> + * Locking: message read. + */ + Collection<MessageId> getUnrestrictedGroupMessages(T txn, AuthorId a) + throws DbException; + /** * Returns the contacts to which the given group is visible. * <p> diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index b37caf2079dd4809dcf25577d859a5c0372392e0..3432df3801cac25b3a8b175615b461ab8a816b24 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -347,7 +347,8 @@ DatabaseCleaner.Callback { * Locking: contact read, message write, rating read. * @param sender is null for a locally generated message. */ - private boolean storeGroupMessage(T txn, Message m, ContactId sender) throws DbException { + private boolean storeGroupMessage(T txn, Message m, ContactId sender) + throws DbException { if(m.getGroup() == null) throw new IllegalArgumentException(); boolean stored = db.addGroupMessage(txn, m, sender != null); if(stored && sender == null) db.setReadFlag(txn, m.getId(), true); @@ -360,9 +361,13 @@ DatabaseCleaner.Callback { if(!c.equals(sender)) db.addStatus(txn, c, id, false); } // Calculate and store the message's sendability - int sendability = calculateSendability(txn, m); - db.setSendability(txn, id, sendability); - if(sendability > 0) updateAncestorSendability(txn, id, true); + if(m.getGroup().isRestricted()) { + db.setSendability(txn, id, 1); + } else { + int sendability = calculateSendability(txn, m); + db.setSendability(txn, id, sendability); + if(sendability > 0) updateAncestorSendability(txn, id, true); + } // Count the bytes stored synchronized(spaceLock) { bytesStoredSinceLastCheck += m.getSerialised().length; @@ -1524,675 +1529,675 @@ DatabaseCleaner.Callback { * <p> * Locking: contact read, message write, rating read, subscription read. */ - private boolean storeMessage(T txn, ContactId c, Message m) - throws DbException { - long now = clock.currentTimeMillis(); - if(m.getTimestamp() > now + MAX_CLOCK_DIFFERENCE) { - if(LOG.isLoggable(INFO)) - LOG.info("Discarding message with future timestamp"); - return false; - } - Group g = m.getGroup(); - if(g == null) return storePrivateMessage(txn, m, c, true); - if(!db.containsVisibleSubscription(txn, c, g.getId())) { - if(LOG.isLoggable(INFO)) - LOG.info("Discarding message without visible subscription"); - return false; - } - return storeGroupMessage(txn, m, c); - } - - public Request receiveOffer(ContactId c, Offer o) throws DbException { - Collection<MessageId> offered; - BitSet request; - contactLock.readLock().lock(); - try { - messageLock.writeLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - offered = o.getMessageIds(); - request = new BitSet(offered.size()); - Iterator<MessageId> it = offered.iterator(); - for(int i = 0; it.hasNext(); i++) { - // If the message is not in the database, or not - // visible to the contact, request it - MessageId m = it.next(); - if(!db.setStatusSeenIfVisible(txn, c, m)) - request.set(i); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - return new Request(request, offered.size()); - } - - public void receiveRetentionAck(ContactId c, RetentionAck a) - throws DbException { - contactLock.readLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setRetentionUpdateAcked(txn, c, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveRetentionUpdate(ContactId c, RetentionUpdate u) - throws DbException { - boolean updated; - contactLock.readLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - updated = db.setRetentionTime(txn, c, u.getRetentionTime(), - u.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c)); - } - - public void receiveSubscriptionAck(ContactId c, SubscriptionAck a) - throws DbException { - contactLock.readLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setSubscriptionUpdateAcked(txn, c, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u) - throws DbException { - boolean updated; - contactLock.readLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<Group> groups = u.getGroups(); - long version = u.getVersion(); - updated = db.setSubscriptions(txn, c, groups, version); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c)); - } - - public void receiveTransportAck(ContactId c, TransportAck a) - throws DbException { - contactLock.readLock().lock(); - try { - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - TransportId t = a.getId(); - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.setTransportUpdateAcked(txn, c, t, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveTransportUpdate(ContactId c, TransportUpdate u) - throws DbException { - boolean updated; - contactLock.readLock().lock(); - try { - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - TransportId t = u.getId(); - TransportProperties p = u.getProperties(); - long version = u.getVersion(); - updated = db.setRemoteProperties(txn, c, t, p, version); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - if(updated) - callListeners(new RemoteTransportsUpdatedEvent(c, u.getId())); - } - - public void removeContact(ContactId c) throws DbException { - contactLock.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.containsContact(txn, c)) - throw new NoSuchContactException(); - db.removeContact(txn, c); - 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 { - contactLock.writeLock().unlock(); - } - callListeners(new ContactRemovedEvent(c)); - } - - public void removeTransport(TransportId t) throws DbException { - transportLock.writeLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.removeTransport(txn, t); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); - } - callListeners(new TransportRemovedEvent(t)); - } - - public void setConnectionWindow(ContactId c, TransportId t, long period, - long centre, byte[] bitmap) throws DbException { - contactLock.readLock().lock(); - try { - transportLock.readLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.setConnectionWindow(txn, c, t, period, centre, - bitmap); - db.setLastConnected(txn, c, clock.currentTimeMillis()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void setRating(AuthorId a, Rating r) throws DbException { - boolean changed; - messageLock.writeLock().lock(); - try { - ratingLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Rating old = db.setRating(txn, a, r); - changed = (old != r); - // Update the sendability of the author's messages - if(r == GOOD && old != GOOD) - updateAuthorSendability(txn, a, true); - else if(r != GOOD && old == GOOD) - updateAuthorSendability(txn, a, false); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - ratingLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - if(changed) callListeners(new RatingChangedEvent(a, r)); - } - - public boolean setReadFlag(MessageId m, boolean read) throws DbException { - messageLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - boolean wasRead = db.setReadFlag(txn, m, read); - db.commitTransaction(txn); - return wasRead; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageLock.writeLock().unlock(); - } - } - - public void setRemoteProperties(ContactId c, - Map<TransportId, TransportProperties> p) throws DbException { - contactLock.readLock().lock(); - try { - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setRemoteProperties(txn, c, p); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void setSeen(ContactId c, Collection<MessageId> seen) - throws DbException { - contactLock.readLock().lock(); - try { - messageLock.writeLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - for(MessageId m : seen) - db.setStatusSeenIfVisible(txn, c, m); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - /** - * Updates the sendability of all messages written by the given author, and - * the ancestors of those messages if necessary. - * <p> - * Locking: message write. - * @param increment true if the user's rating for the author has changed - * from not good to good, or false if it has changed from good to not good. - */ - private void updateAuthorSendability(T txn, AuthorId a, boolean increment) - throws DbException { - for(MessageId id : db.getMessagesByAuthor(txn, a)) { - int sendability = db.getSendability(txn, id); - if(increment) { - db.setSendability(txn, id, sendability + 1); - if(sendability == 0) - updateAncestorSendability(txn, id, true); - } else { - assert sendability > 0; - db.setSendability(txn, id, sendability - 1); - if(sendability == 1) - updateAncestorSendability(txn, id, false); - } - } - } - - public boolean setStarredFlag(MessageId m, boolean starred) - throws DbException { - messageLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - boolean wasStarred = db.setStarredFlag(txn, m, starred); - db.commitTransaction(txn); - return wasStarred; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageLock.writeLock().unlock(); - } - } - - public void setVisibility(GroupId g, Collection<ContactId> visible) - throws DbException { - Collection<ContactId> affected = new ArrayList<ContactId>(); - contactLock.readLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsSubscription(txn, g)) - throw new NoSuchSubscriptionException(); - // Use HashSets for O(1) lookups, O(n) overall running time - HashSet<ContactId> now = new HashSet<ContactId>(visible); - Collection<ContactId> before = db.getVisibility(txn, g); - before = new HashSet<ContactId>(before); - // Set the group's visibility for each current contact - for(ContactId c : db.getContactIds(txn)) { - boolean wasBefore = before.contains(c); - boolean isNow = now.contains(c); - if(!wasBefore && isNow) { - db.addVisibility(txn, c, g); - affected.add(c); - } else if(wasBefore && !isNow) { - db.removeVisibility(txn, c, g); - affected.add(c); - } - } - // Make the group invisible to future contacts - db.setVisibleToAll(txn, g, false); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - if(!affected.isEmpty()) - callListeners(new LocalSubscriptionsUpdatedEvent(affected)); - } - - public void setVisibleToAll(GroupId g, boolean visible) throws DbException { - Collection<ContactId> affected = new ArrayList<ContactId>(); - contactLock.readLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsSubscription(txn, g)) - throw new NoSuchSubscriptionException(); - // Make the group visible or invisible to future contacts - db.setVisibleToAll(txn, g, visible); - if(visible) { - // Make the group visible to all current contacts - Collection<ContactId> before = db.getVisibility(txn, g); - before = new HashSet<ContactId>(before); - for(ContactId c : db.getContactIds(txn)) { - if(!before.contains(c)) { - db.addVisibility(txn, c, g); - affected.add(c); - } - } - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - if(!affected.isEmpty()) - callListeners(new LocalSubscriptionsUpdatedEvent(affected)); - } - - public boolean subscribe(Group g) throws DbException { - boolean added = false; - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsSubscription(txn, g.getId())) - added = db.addSubscription(txn, g); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - if(added) callListeners(new SubscriptionAddedEvent(g)); - return added; - } - - public void unsubscribe(Group g) throws DbException { - Collection<ContactId> affected; - messageLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - GroupId id = g.getId(); - if(!db.containsSubscription(txn, id)) - throw new NoSuchSubscriptionException(); - affected = db.getVisibility(txn, id); - db.removeSubscription(txn, id); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - callListeners(new SubscriptionRemovedEvent(g)); - callListeners(new LocalSubscriptionsUpdatedEvent(affected)); - } - - public void checkFreeSpaceAndClean() throws DbException { - long freeSpace = db.getFreeSpace(); - if(LOG.isLoggable(INFO)) LOG.info(freeSpace + " bytes free space"); - while(freeSpace < MIN_FREE_SPACE) { - boolean expired = expireMessages(BYTES_PER_SWEEP); - if(freeSpace < CRITICAL_FREE_SPACE && !expired) { - // FIXME: Work out what to do here - throw new Error("Disk space is critically low"); - } - Thread.yield(); - freeSpace = db.getFreeSpace(); - } - } - - /** - * Removes the oldest messages from the database, with a total size less - * than or equal to the given size, and returns true if any messages were - * removed. - */ - private boolean expireMessages(int size) throws DbException { - Collection<MessageId> expired; - messageLock.writeLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - expired = db.getOldMessages(txn, size); - if(!expired.isEmpty()) { - for(MessageId m : expired) removeMessage(txn, m); - db.incrementRetentionVersions(txn); - if(LOG.isLoggable(INFO)) - LOG.info("Expired " + expired.size() + " messages"); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - if(expired.isEmpty()) return false; - callListeners(new MessageExpiredEvent()); - return true; - } - - /** - * Removes the given message (and all associated state) from the database. - * <p> - * Locking: message write. - */ - private void removeMessage(T txn, MessageId m) throws DbException { - int sendability = db.getSendability(txn, m); - // If the message is sendable, deleting it may affect its ancestors' - // sendability (backward inclusion) - if(sendability > 0) updateAncestorSendability(txn, m, false); - db.removeMessage(txn, m); - } - - public boolean shouldCheckFreeSpace() { - synchronized(spaceLock) { - long now = clock.currentTimeMillis(); - if(bytesStoredSinceLastCheck > MAX_BYTES_BETWEEN_SPACE_CHECKS - || now - timeOfLastCheck > MAX_MS_BETWEEN_SPACE_CHECKS) { - bytesStoredSinceLastCheck = 0; - timeOfLastCheck = now; - return true; - } - } - return false; - } + private boolean storeMessage(T txn, ContactId c, Message m) + throws DbException { + long now = clock.currentTimeMillis(); + if(m.getTimestamp() > now + MAX_CLOCK_DIFFERENCE) { + if(LOG.isLoggable(INFO)) + LOG.info("Discarding message with future timestamp"); + return false; + } + Group g = m.getGroup(); + if(g == null) return storePrivateMessage(txn, m, c, true); + if(!db.containsVisibleSubscription(txn, c, g.getId())) { + if(LOG.isLoggable(INFO)) + LOG.info("Discarding message without visible subscription"); + return false; + } + return storeGroupMessage(txn, m, c); + } + + public Request receiveOffer(ContactId c, Offer o) throws DbException { + Collection<MessageId> offered; + BitSet request; + contactLock.readLock().lock(); + try { + messageLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + offered = o.getMessageIds(); + request = new BitSet(offered.size()); + Iterator<MessageId> it = offered.iterator(); + for(int i = 0; it.hasNext(); i++) { + // If the message is not in the database, or not + // visible to the contact, request it + MessageId m = it.next(); + if(!db.setStatusSeenIfVisible(txn, c, m)) + request.set(i); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + return new Request(request, offered.size()); + } + + public void receiveRetentionAck(ContactId c, RetentionAck a) + throws DbException { + contactLock.readLock().lock(); + try { + retentionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setRetentionUpdateAcked(txn, c, a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + retentionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveRetentionUpdate(ContactId c, RetentionUpdate u) + throws DbException { + boolean updated; + contactLock.readLock().lock(); + try { + retentionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + updated = db.setRetentionTime(txn, c, u.getRetentionTime(), + u.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + retentionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c)); + } + + public void receiveSubscriptionAck(ContactId c, SubscriptionAck a) + throws DbException { + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setSubscriptionUpdateAcked(txn, c, a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u) + throws DbException { + boolean updated; + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + Collection<Group> groups = u.getGroups(); + long version = u.getVersion(); + updated = db.setSubscriptions(txn, c, groups, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c)); + } + + public void receiveTransportAck(ContactId c, TransportAck a) + throws DbException { + contactLock.readLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + TransportId t = a.getId(); + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.setTransportUpdateAcked(txn, c, t, a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveTransportUpdate(ContactId c, TransportUpdate u) + throws DbException { + boolean updated; + contactLock.readLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + TransportId t = u.getId(); + TransportProperties p = u.getProperties(); + long version = u.getVersion(); + updated = db.setRemoteProperties(txn, c, t, p, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + if(updated) + callListeners(new RemoteTransportsUpdatedEvent(c, u.getId())); + } + + public void removeContact(ContactId c) throws DbException { + contactLock.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.containsContact(txn, c)) + throw new NoSuchContactException(); + db.removeContact(txn, c); + 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 { + contactLock.writeLock().unlock(); + } + callListeners(new ContactRemovedEvent(c)); + } + + public void removeTransport(TransportId t) throws DbException { + transportLock.writeLock().lock(); + try { + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.removeTransport(txn, t); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.writeLock().unlock(); + } + callListeners(new TransportRemovedEvent(t)); + } + + public void setConnectionWindow(ContactId c, TransportId t, long period, + long centre, byte[] bitmap) throws DbException { + contactLock.readLock().lock(); + try { + transportLock.readLock().lock(); + try { + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.setConnectionWindow(txn, c, t, period, centre, + bitmap); + db.setLastConnected(txn, c, clock.currentTimeMillis()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void setRating(AuthorId a, Rating r) throws DbException { + boolean changed; + messageLock.writeLock().lock(); + try { + ratingLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Rating old = db.setRating(txn, a, r); + changed = (old != r); + // Update the sendability of the author's messages + if(r == GOOD && old != GOOD) + updateAuthorSendability(txn, a, true); + else if(r != GOOD && old == GOOD) + updateAuthorSendability(txn, a, false); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + ratingLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + if(changed) callListeners(new RatingChangedEvent(a, r)); + } + + public boolean setReadFlag(MessageId m, boolean read) throws DbException { + messageLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + boolean wasRead = db.setReadFlag(txn, m, read); + db.commitTransaction(txn); + return wasRead; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageLock.writeLock().unlock(); + } + } + + public void setRemoteProperties(ContactId c, + Map<TransportId, TransportProperties> p) throws DbException { + contactLock.readLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setRemoteProperties(txn, c, p); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void setSeen(ContactId c, Collection<MessageId> seen) + throws DbException { + contactLock.readLock().lock(); + try { + messageLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + for(MessageId m : seen) + db.setStatusSeenIfVisible(txn, c, m); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + /** + * Updates the sendability of all messages posted by the given author to + * unrestricted groups, and the ancestors of those messages if necessary. + * <p> + * Locking: message write. + * @param increment true if the user's rating for the author has changed + * from not good to good, or false if it has changed from good to not good. + */ + private void updateAuthorSendability(T txn, AuthorId a, boolean increment) + throws DbException { + for(MessageId id : db.getUnrestrictedGroupMessages(txn, a)) { + int sendability = db.getSendability(txn, id); + if(increment) { + db.setSendability(txn, id, sendability + 1); + if(sendability == 0) + updateAncestorSendability(txn, id, true); + } else { + assert sendability > 0; + db.setSendability(txn, id, sendability - 1); + if(sendability == 1) + updateAncestorSendability(txn, id, false); + } + } + } + + public boolean setStarredFlag(MessageId m, boolean starred) + throws DbException { + messageLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + boolean wasStarred = db.setStarredFlag(txn, m, starred); + db.commitTransaction(txn); + return wasStarred; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageLock.writeLock().unlock(); + } + } + + public void setVisibility(GroupId g, Collection<ContactId> visible) + throws DbException { + Collection<ContactId> affected = new ArrayList<ContactId>(); + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsSubscription(txn, g)) + throw new NoSuchSubscriptionException(); + // Use HashSets for O(1) lookups, O(n) overall running time + HashSet<ContactId> now = new HashSet<ContactId>(visible); + Collection<ContactId> before = db.getVisibility(txn, g); + before = new HashSet<ContactId>(before); + // Set the group's visibility for each current contact + for(ContactId c : db.getContactIds(txn)) { + boolean wasBefore = before.contains(c); + boolean isNow = now.contains(c); + if(!wasBefore && isNow) { + db.addVisibility(txn, c, g); + affected.add(c); + } else if(wasBefore && !isNow) { + db.removeVisibility(txn, c, g); + affected.add(c); + } + } + // Make the group invisible to future contacts + db.setVisibleToAll(txn, g, false); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + if(!affected.isEmpty()) + callListeners(new LocalSubscriptionsUpdatedEvent(affected)); + } + + public void setVisibleToAll(GroupId g, boolean visible) throws DbException { + Collection<ContactId> affected = new ArrayList<ContactId>(); + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsSubscription(txn, g)) + throw new NoSuchSubscriptionException(); + // Make the group visible or invisible to future contacts + db.setVisibleToAll(txn, g, visible); + if(visible) { + // Make the group visible to all current contacts + Collection<ContactId> before = db.getVisibility(txn, g); + before = new HashSet<ContactId>(before); + for(ContactId c : db.getContactIds(txn)) { + if(!before.contains(c)) { + db.addVisibility(txn, c, g); + affected.add(c); + } + } + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + if(!affected.isEmpty()) + callListeners(new LocalSubscriptionsUpdatedEvent(affected)); + } + + public boolean subscribe(Group g) throws DbException { + boolean added = false; + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsSubscription(txn, g.getId())) + added = db.addSubscription(txn, g); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + if(added) callListeners(new SubscriptionAddedEvent(g)); + return added; + } + + public void unsubscribe(Group g) throws DbException { + Collection<ContactId> affected; + messageLock.writeLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + GroupId id = g.getId(); + if(!db.containsSubscription(txn, id)) + throw new NoSuchSubscriptionException(); + affected = db.getVisibility(txn, id); + db.removeSubscription(txn, id); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + callListeners(new SubscriptionRemovedEvent(g)); + callListeners(new LocalSubscriptionsUpdatedEvent(affected)); + } + + public void checkFreeSpaceAndClean() throws DbException { + long freeSpace = db.getFreeSpace(); + if(LOG.isLoggable(INFO)) LOG.info(freeSpace + " bytes free space"); + while(freeSpace < MIN_FREE_SPACE) { + boolean expired = expireMessages(BYTES_PER_SWEEP); + if(freeSpace < CRITICAL_FREE_SPACE && !expired) { + // FIXME: Work out what to do here + throw new Error("Disk space is critically low"); + } + Thread.yield(); + freeSpace = db.getFreeSpace(); + } + } + + /** + * Removes the oldest messages from the database, with a total size less + * than or equal to the given size, and returns true if any messages were + * removed. + */ + private boolean expireMessages(int size) throws DbException { + Collection<MessageId> expired; + messageLock.writeLock().lock(); + try { + retentionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + expired = db.getOldMessages(txn, size); + if(!expired.isEmpty()) { + for(MessageId m : expired) removeMessage(txn, m); + db.incrementRetentionVersions(txn); + if(LOG.isLoggable(INFO)) + LOG.info("Expired " + expired.size() + " messages"); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + retentionLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + if(expired.isEmpty()) return false; + callListeners(new MessageExpiredEvent()); + return true; + } + + /** + * Removes the given message (and all associated state) from the database. + * <p> + * Locking: message write. + */ + private void removeMessage(T txn, MessageId m) throws DbException { + int sendability = db.getSendability(txn, m); + // If the message is sendable, deleting it may affect its ancestors' + // sendability (backward inclusion) + if(sendability > 0) updateAncestorSendability(txn, m, false); + db.removeMessage(txn, m); + } + + public boolean shouldCheckFreeSpace() { + synchronized(spaceLock) { + long now = clock.currentTimeMillis(); + if(bytesStoredSinceLastCheck > MAX_BYTES_BETWEEN_SPACE_CHECKS + || now - timeOfLastCheck > MAX_MS_BETWEEN_SPACE_CHECKS) { + bytesStoredSinceLastCheck = 0; + timeOfLastCheck = now; + return true; + } + } + return false; + } } diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index c54831de2d998ac6713036d312920bc5374a0d34..caf7cec3da826fa97fc7ed41d6a6ae40852be450 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -1601,27 +1601,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Collection<MessageId> getMessagesByAuthor(Connection txn, AuthorId a) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT messageId FROM messages WHERE authorId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, a.getBytes()); - rs = ps.executeQuery(); - List<MessageId> ids = new ArrayList<MessageId>(); - while(rs.next()) ids.add(new MessageId(rs.getBytes(1))); - rs.close(); - ps.close(); - return Collections.unmodifiableList(ids); - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c, int maxMessages) throws DbException { PreparedStatement ps = null; @@ -2568,6 +2547,32 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Collection<MessageId> getUnrestrictedGroupMessages(Connection txn, + AuthorId a) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT messageId" + + " FROM messages AS m" + + " JOIN groups AS g" + + " ON m.groupId = g.groupId" + + " WHERE authorId = ?" + + " AND publicKey IS NULL"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, a.getBytes()); + rs = ps.executeQuery(); + List<MessageId> ids = new ArrayList<MessageId>(); + while(rs.next()) ids.add(new MessageId(rs.getBytes(1))); + rs.close(); + ps.close(); + return Collections.unmodifiableList(ids); + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<ContactId> getVisibility(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index fbb0fdda958dabf8f584f491f6b23a9ec1b272fa..a06a3c81ee25a5d91401452c67d8a13dc636616d 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -59,13 +59,13 @@ import org.junit.Test; public abstract class DatabaseComponentTest extends BriarTestCase { protected final Object txn = new Object(); - protected final GroupId groupId; - protected final Group group; + protected final GroupId groupId, restrictedGroupId; + protected final Group group, restrictedGroup; protected final AuthorId authorId; protected final Author author; protected final AuthorId localAuthorId; protected final LocalAuthor localAuthor; - protected final MessageId messageId, messageId1; + protected final MessageId messageId, messageId1, privateMessageId; protected final String contentType, subject; protected final long timestamp; protected final int size; @@ -81,7 +81,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public DatabaseComponentTest() { super(); groupId = new GroupId(TestUtils.getRandomId()); + restrictedGroupId = new GroupId(TestUtils.getRandomId()); group = new Group(groupId, "Group name", null); + restrictedGroup = new Group(restrictedGroupId, "Restricted group name", + new byte[60]); authorId = new AuthorId(TestUtils.getRandomId()); author = new Author(authorId, "Alice", new byte[60]); localAuthorId = new AuthorId(TestUtils.getRandomId()); @@ -89,6 +92,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { new byte[60]); messageId = new MessageId(TestUtils.getRandomId()); messageId1 = new MessageId(TestUtils.getRandomId()); + privateMessageId = new MessageId(TestUtils.getRandomId()); contentType = "text/plain"; subject = "Foo"; timestamp = System.currentTimeMillis(); @@ -96,7 +100,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { raw = new byte[size]; message = new TestMessage(messageId, null, group, author, contentType, subject, timestamp, raw); - privateMessage = new TestMessage(messageId, null, null, null, + privateMessage = new TestMessage(privateMessageId, null, null, null, contentType, subject, timestamp, raw); transportId = new TransportId(TestUtils.getRandomId()); transportProperties = new TransportProperties(Collections.singletonMap( @@ -139,7 +143,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // setRating(authorId, GOOD) oneOf(database).setRating(txn, authorId, GOOD); will(returnValue(UNRATED)); - oneOf(database).getMessagesByAuthor(txn, authorId); + oneOf(database).getUnrestrictedGroupMessages(txn, authorId); will(returnValue(Collections.emptyList())); oneOf(listener).eventOccurred(with(any(RatingChangedEvent.class))); // setRating(authorId, GOOD) again @@ -223,6 +227,59 @@ public abstract class DatabaseComponentTest extends BriarTestCase { context.assertIsSatisfied(); } + @Test + public void testRestrictedGroupMessagesAreAlwaysSendable() + throws Exception { + final Message groupMessage = new TestMessage(messageId, null, + restrictedGroup, author, contentType, subject, timestamp, raw); + final Message groupMessage1 = new TestMessage(messageId1, null, + restrictedGroup, null, contentType, subject, timestamp, raw); + 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() {{ + // addLocalGroupMessage(groupMessage) + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).containsSubscription(txn, restrictedGroupId); + will(returnValue(true)); + oneOf(database).addGroupMessage(txn, groupMessage, false); + will(returnValue(true)); + oneOf(database).setReadFlag(txn, messageId, true); + oneOf(database).getContactIds(txn); + will(returnValue(Arrays.asList(contactId))); + oneOf(database).addStatus(txn, contactId, messageId, false); + oneOf(database).setSendability(txn, messageId, 1); + oneOf(database).commitTransaction(txn); + // receiveMessage(groupMessage1) + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).containsContact(txn, contactId); + will(returnValue(true)); + oneOf(database).containsVisibleSubscription(txn, contactId, + restrictedGroupId); + will(returnValue(true)); + oneOf(database).addGroupMessage(txn, groupMessage1, true); + will(returnValue(true)); + oneOf(database).addStatus(txn, contactId, messageId1, true); + oneOf(database).getContactIds(txn); + will(returnValue(Arrays.asList(contactId))); + oneOf(database).setSendability(txn, messageId1, 1); + oneOf(database).addMessageToAck(txn, contactId, messageId1); + oneOf(database).commitTransaction(txn); + }}); + + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); + + db.addLocalGroupMessage(groupMessage); + db.receiveMessage(contactId, groupMessage1); + + context.assertIsSatisfied(); + } + @Test public void testNullParentStopsBackwardInclusion() throws Exception { Mockery context = new Mockery(); @@ -237,7 +294,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setRating(txn, authorId, GOOD); will(returnValue(UNRATED)); // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); + oneOf(database).getUnrestrictedGroupMessages(txn, authorId); will(returnValue(Arrays.asList(messageId))); oneOf(database).getSendability(txn, messageId); will(returnValue(0)); @@ -269,7 +326,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setRating(txn, authorId, GOOD); will(returnValue(UNRATED)); // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); + oneOf(database).getUnrestrictedGroupMessages(txn, authorId); will(returnValue(Arrays.asList(messageId))); oneOf(database).getSendability(txn, messageId); will(returnValue(0)); @@ -306,7 +363,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setRating(txn, authorId, GOOD); will(returnValue(UNRATED)); // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); + oneOf(database).getUnrestrictedGroupMessages(txn, authorId); will(returnValue(Arrays.asList(messageId))); oneOf(database).getSendability(txn, messageId); will(returnValue(0)); @@ -497,8 +554,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addPrivateMessage(txn, privateMessage, contactId, false); will(returnValue(true)); - oneOf(database).setReadFlag(txn, messageId, true); - oneOf(database).addStatus(txn, contactId, messageId, false); + oneOf(database).setReadFlag(txn, privateMessageId, true); + oneOf(database).addStatus(txn, contactId, privateMessageId, false); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, shutdown); @@ -1154,9 +1211,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addPrivateMessage(txn, privateMessage, contactId, true); will(returnValue(true)); - oneOf(database).addStatus(txn, contactId, messageId, true); + oneOf(database).addStatus(txn, contactId, privateMessageId, true); // The message must be acked - oneOf(database).addMessageToAck(txn, contactId, messageId); + oneOf(database).addMessageToAck(txn, contactId, privateMessageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, shutdown); @@ -1184,7 +1241,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { true); will(returnValue(false)); // The message must still be acked - oneOf(database).addMessageToAck(txn, contactId, messageId); + oneOf(database).addMessageToAck(txn, contactId, privateMessageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, shutdown); @@ -1561,8 +1618,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addPrivateMessage(txn, privateMessage, contactId, false); will(returnValue(true)); - oneOf(database).setReadFlag(txn, messageId, true); - oneOf(database).addStatus(txn, contactId, messageId, false); + oneOf(database).setReadFlag(txn, privateMessageId, true); + oneOf(database).addStatus(txn, contactId, privateMessageId, false); // The message was added, so the listener should be called oneOf(listener).eventOccurred(with(any( PrivateMessageAddedEvent.class))); diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java index b2c8e45be3cd07e25887016b4bf9dfaa5dbd595a..65b53ff1be634d71f11e5c20fbc2ecb2c49d82b5 100644 --- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java +++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java @@ -534,27 +534,39 @@ public class H2DatabaseTest extends BriarTestCase { } @Test - public void testGetMessagesByAuthor() throws Exception { + public void testGetUnrestrictedGroupMessages() throws Exception { AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); Author author1 = new Author(authorId1, "Bob", new byte[60]); MessageId messageId1 = new MessageId(TestUtils.getRandomId()); Message message1 = new TestMessage(messageId1, null, group, author1, contentType, subject, timestamp, raw); + GroupId groupId1 = new GroupId(TestUtils.getRandomId()); + Group group1 = new Group(groupId1, "Restricted group name", + new byte[60]); + MessageId messageId2 = new MessageId(TestUtils.getRandomId()); + Message message2 = new TestMessage(messageId2, null, group1, author, + contentType, subject, timestamp, raw); Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Subscribe to a group and store two messages + // Subscribe to an unrestricted group and store two messages db.addSubscription(txn, group); db.addGroupMessage(txn, message, false); db.addGroupMessage(txn, message1, false); - // Check that each message is retrievable via its author - Iterator<MessageId> it = - db.getMessagesByAuthor(txn, authorId).iterator(); + // Subscribe to a restricted group and store a message + db.addSubscription(txn, group1); + db.addGroupMessage(txn, message2, false); + + // Check that only the messages in the unrestricted group are retrieved + Collection<MessageId> ids = db.getUnrestrictedGroupMessages(txn, + authorId); + Iterator<MessageId> it = ids.iterator(); assertTrue(it.hasNext()); assertEquals(messageId, it.next()); assertFalse(it.hasNext()); - it = db.getMessagesByAuthor(txn, authorId1).iterator(); + ids = db.getUnrestrictedGroupMessages(txn, authorId1); + it = ids.iterator(); assertTrue(it.hasNext()); assertEquals(messageId1, it.next()); assertFalse(it.hasNext());