mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Replaced private messages with private groups.
Private messages are now the same as group messages, but groups can be private or public. When a contact is added, a private group is created and designated as the inbox for exchanging private messages with the contact.
This commit is contained in:
@@ -52,6 +52,7 @@
|
||||
<string name="messages_title">Messages</string>
|
||||
<string name="no_messages">(No messages)</string>
|
||||
<string name="format_from">From: %1$s</string>
|
||||
<string name="format_to">To: %1$s</string>
|
||||
<string name="new_message_title">New Message</string>
|
||||
<string name="from">From:</string>
|
||||
<string name="to">To:</string>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package net.sf.briar.android;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
|
||||
public class DescendingHeaderComparator implements Comparator<MessageHeader> {
|
||||
|
||||
public static final DescendingHeaderComparator INSTANCE =
|
||||
new DescendingHeaderComparator();
|
||||
|
||||
public int compare(MessageHeader a, MessageHeader b) {
|
||||
// The newest message comes first
|
||||
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
|
||||
if(aTime > bTime) return -1;
|
||||
if(aTime < bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import static android.widget.LinearLayout.VERTICAL;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.WRAP_WRAP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -188,13 +187,8 @@ public class SetupActivity extends RoboActivity implements OnClickListener {
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
final byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
final byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||
LocalAuthor a;
|
||||
try {
|
||||
a = authorFactory.createLocalAuthor(nickname, publicKey,
|
||||
privateKey);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
LocalAuthor a = authorFactory.createLocalAuthor(nickname,
|
||||
publicKey, privateKey);
|
||||
showHomeScreen(referenceManager.putReference(a,
|
||||
LocalAuthor.class));
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import net.sf.briar.android.groups.NoContactsDialog;
|
||||
import net.sf.briar.android.invitation.AddContactActivity;
|
||||
import net.sf.briar.android.util.HorizontalBorder;
|
||||
import net.sf.briar.android.util.HorizontalSpace;
|
||||
@@ -34,32 +33,29 @@ import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.android.DatabaseUiExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchContactException;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.event.ContactAddedEvent;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
import net.sf.briar.api.transport.ConnectionListener;
|
||||
import net.sf.briar.api.transport.ConnectionRegistry;
|
||||
import roboguice.activity.RoboFragmentActivity;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class ContactListActivity extends RoboFragmentActivity
|
||||
implements OnClickListener, DatabaseListener, ConnectionListener,
|
||||
NoContactsDialog.Listener {
|
||||
public class ContactListActivity extends RoboActivity
|
||||
implements OnClickListener, DatabaseListener, ConnectionListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactListActivity.class.getName());
|
||||
@@ -68,9 +64,7 @@ NoContactsDialog.Listener {
|
||||
private ContactListAdapter adapter = null;
|
||||
private ListView list = null;
|
||||
private ListLoadingProgressBar loading = null;
|
||||
private ImageButton addContactButton = null, composeButton = null;
|
||||
private ImageButton shareButton = null;
|
||||
private NoContactsDialog noContactsDialog = null;
|
||||
private ImageButton addContactButton = null, shareButton = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@@ -113,13 +107,6 @@ NoContactsDialog.Listener {
|
||||
footer.addView(addContactButton);
|
||||
footer.addView(new HorizontalSpace(this));
|
||||
|
||||
composeButton = new ImageButton(this);
|
||||
composeButton.setBackgroundResource(0);
|
||||
composeButton.setImageResource(R.drawable.content_new_email);
|
||||
composeButton.setOnClickListener(this);
|
||||
footer.addView(composeButton);
|
||||
footer.addView(new HorizontalSpace(this));
|
||||
|
||||
shareButton = new ImageButton(this);
|
||||
shareButton.setBackgroundResource(0);
|
||||
shareButton.setImageResource(R.drawable.social_share);
|
||||
@@ -129,12 +116,6 @@ NoContactsDialog.Listener {
|
||||
layout.addView(footer);
|
||||
|
||||
setContentView(layout);
|
||||
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
Fragment f = fm.findFragmentByTag("NoContactsDialog");
|
||||
if(f == null) noContactsDialog = new NoContactsDialog();
|
||||
else noContactsDialog = (NoContactsDialog) f;
|
||||
noContactsDialog.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,11 +123,11 @@ NoContactsDialog.Listener {
|
||||
super.onResume();
|
||||
db.addListener(this);
|
||||
connectionRegistry.addListener(this);
|
||||
loadHeaders();
|
||||
loadContacts();
|
||||
}
|
||||
|
||||
private void loadHeaders() {
|
||||
clearHeaders();
|
||||
private void loadContacts() {
|
||||
clearContacts();
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
@@ -157,9 +138,9 @@ NoContactsDialog.Listener {
|
||||
Long lastConnected = times.get(c.getId());
|
||||
if(lastConnected == null) continue;
|
||||
try {
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
db.getPrivateMessageHeaders(c.getId());
|
||||
displayHeaders(c, lastConnected, headers);
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(c.getId());
|
||||
displayContact(c, lastConnected, headers);
|
||||
} catch(NoSuchContactException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Contact removed");
|
||||
@@ -181,7 +162,7 @@ NoContactsDialog.Listener {
|
||||
});
|
||||
}
|
||||
|
||||
private void clearHeaders() {
|
||||
private void clearContacts() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(GONE);
|
||||
@@ -192,8 +173,8 @@ NoContactsDialog.Listener {
|
||||
});
|
||||
}
|
||||
|
||||
private void displayHeaders(final Contact c, final long lastConnected,
|
||||
final Collection<PrivateMessageHeader> headers) {
|
||||
private void displayContact(final Contact c, final long lastConnected,
|
||||
final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
@@ -239,14 +220,6 @@ NoContactsDialog.Listener {
|
||||
public void onClick(View view) {
|
||||
if(view == addContactButton) {
|
||||
startActivity(new Intent(this, AddContactActivity.class));
|
||||
} else if(view == composeButton) {
|
||||
if(adapter.isEmpty()) {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
noContactsDialog.show(fm, "NoContactsDialog");
|
||||
} else {
|
||||
startActivity(new Intent(this,
|
||||
WritePrivateMessageActivity.class));
|
||||
}
|
||||
} else if(view == shareButton) {
|
||||
String apkPath = getPackageCodePath();
|
||||
Intent i = new Intent(ACTION_SEND);
|
||||
@@ -259,28 +232,28 @@ NoContactsDialog.Listener {
|
||||
|
||||
public void eventOccurred(DatabaseEvent e) {
|
||||
if(e instanceof ContactAddedEvent) {
|
||||
loadHeaders();
|
||||
loadContacts();
|
||||
} else if(e instanceof ContactRemovedEvent) {
|
||||
// Reload the conversation, expecting NoSuchContactException
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed, reloading");
|
||||
reloadHeaders(((ContactRemovedEvent) e).getContactId());
|
||||
reloadContact(((ContactRemovedEvent) e).getContactId());
|
||||
} else if(e instanceof MessageAddedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
reloadContact(((MessageAddedEvent) e).getContactId());
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
} else if(e instanceof PrivateMessageAddedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
reloadHeaders(((PrivateMessageAddedEvent) e).getContactId());
|
||||
loadContacts();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadHeaders(final ContactId c) {
|
||||
private void reloadContact(final ContactId c) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
db.getPrivateMessageHeaders(c);
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(c);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Partial load took " + duration + " ms");
|
||||
@@ -301,18 +274,11 @@ NoContactsDialog.Listener {
|
||||
}
|
||||
|
||||
private void updateItem(final ContactId c,
|
||||
final Collection<PrivateMessageHeader> headers) {
|
||||
final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ContactListItem item = findItem(c);
|
||||
if(item == null) return;
|
||||
// Replace the item with a new item containing the new headers
|
||||
adapter.remove(item);
|
||||
item = new ContactListItem(item.getContact(),
|
||||
item.isConnected(), item.getLastConnected(), headers);
|
||||
adapter.add(item);
|
||||
adapter.sort(ItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
if(item != null) item.setHeaders(headers);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -337,28 +303,16 @@ NoContactsDialog.Listener {
|
||||
private void setConnected(final ContactId c, final boolean connected) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
int count = adapter.getCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
ContactListItem item = adapter.getItem(i);
|
||||
if(item.getContactId().equals(c)) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Updating connection time");
|
||||
item.setConnected(connected);
|
||||
item.setLastConnected(System.currentTimeMillis());
|
||||
list.invalidateViews();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ContactListItem item = findItem(c);
|
||||
if(item == null) return;
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Updating connection time");
|
||||
item.setConnected(connected);
|
||||
item.setLastConnected(System.currentTimeMillis());
|
||||
list.invalidateViews();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void contactCreationSelected() {
|
||||
startActivity(new Intent(this, AddContactActivity.class));
|
||||
}
|
||||
|
||||
public void contactCreationCancelled() {}
|
||||
|
||||
private static class ItemComparator implements Comparator<ContactListItem> {
|
||||
|
||||
private static final ItemComparator INSTANCE = new ItemComparator();
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package net.sf.briar.android.contact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.briar.android.DescendingHeaderComparator;
|
||||
import net.sf.briar.api.AuthorId;
|
||||
import net.sf.briar.api.Contact;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ContactListItem {
|
||||
@@ -17,27 +13,27 @@ class ContactListItem {
|
||||
private final Contact contact;
|
||||
private boolean connected;
|
||||
private long lastConnected;
|
||||
private final boolean empty;
|
||||
private final long timestamp;
|
||||
private final int unread;
|
||||
private boolean empty;
|
||||
private long timestamp;
|
||||
private int unread;
|
||||
|
||||
ContactListItem(Contact contact, boolean connected, long lastConnected,
|
||||
Collection<PrivateMessageHeader> headers) {
|
||||
Collection<MessageHeader> headers) {
|
||||
this.contact = contact;
|
||||
this.connected = connected;
|
||||
this.lastConnected = lastConnected;
|
||||
setHeaders(headers);
|
||||
}
|
||||
|
||||
void setHeaders(Collection<MessageHeader> headers) {
|
||||
empty = headers.isEmpty();
|
||||
if(empty) {
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
} else {
|
||||
List<PrivateMessageHeader> list =
|
||||
new ArrayList<PrivateMessageHeader>(headers);
|
||||
Collections.sort(list, DescendingHeaderComparator.INSTANCE);
|
||||
timestamp = list.get(0).getTimestamp();
|
||||
int unread = 0;
|
||||
for(PrivateMessageHeader h : list) if(!h.isRead()) unread++;
|
||||
this.unread = unread;
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
if(!empty) {
|
||||
for(MessageHeader h : headers) {
|
||||
if(h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
|
||||
if(!h.isRead()) unread++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package net.sf.briar.android.contact;
|
||||
|
||||
import static net.sf.briar.android.contact.ContactItem.NEW;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ContactSpinnerAdapter extends BaseAdapter
|
||||
implements SpinnerAdapter {
|
||||
|
||||
private final Context ctx;
|
||||
private final List<ContactItem> list = new ArrayList<ContactItem>();
|
||||
|
||||
public ContactSpinnerAdapter(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public void add(ContactItem item) {
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return list.isEmpty() ? 0 : list.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView,
|
||||
ViewGroup parent) {
|
||||
return getView(position, convertView, parent);
|
||||
}
|
||||
|
||||
public ContactItem getItem(int position) {
|
||||
if(position == list.size()) return NEW;
|
||||
return list.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return android.R.layout.simple_spinner_item;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
TextView name = new TextView(ctx);
|
||||
name.setTextSize(18);
|
||||
name.setMaxLines(1);
|
||||
Resources res = ctx.getResources();
|
||||
int pad = res.getInteger(R.integer.spinner_padding);
|
||||
name.setPadding(pad, pad, pad, pad);
|
||||
ContactItem item = getItem(position);
|
||||
if(item == NEW) name.setText(R.string.new_contact_item);
|
||||
else name.setText(item.getContact().getAuthor().getName());
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public void sort(Comparator<ContactItem> comparator) {
|
||||
Collections.sort(list, comparator);
|
||||
}
|
||||
}
|
||||
@@ -16,22 +16,21 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import net.sf.briar.android.AscendingHeaderComparator;
|
||||
import net.sf.briar.android.util.HorizontalBorder;
|
||||
import net.sf.briar.android.util.ListLoadingProgressBar;
|
||||
import net.sf.briar.api.AuthorId;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.android.DatabaseUiExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchContactException;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -53,13 +52,14 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
private ConversationAdapter adapter = null;
|
||||
private ListView list = null;
|
||||
private ListLoadingProgressBar loading = null;
|
||||
private ImageButton composeButton = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
private volatile ContactId contactId = null;
|
||||
private volatile AuthorId localAuthorId = null;
|
||||
private volatile GroupId groupId = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -72,9 +72,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
|
||||
if(contactName == null) throw new IllegalStateException();
|
||||
setTitle(contactName);
|
||||
byte[] b = i.getByteArrayExtra("net.sf.briar.LOCAL_AUTHOR_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
localAuthorId = new AuthorId(b);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
@@ -96,9 +93,10 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
|
||||
layout.addView(new HorizontalBorder(this));
|
||||
|
||||
ImageButton composeButton = new ImageButton(this);
|
||||
composeButton = new ImageButton(this);
|
||||
composeButton.setBackgroundResource(0);
|
||||
composeButton.setImageResource(R.drawable.content_new_email);
|
||||
composeButton.setEnabled(false); // Enabled after loading the headers
|
||||
composeButton.setOnClickListener(this);
|
||||
layout.addView(composeButton);
|
||||
|
||||
@@ -118,8 +116,9 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
db.getPrivateMessageHeaders(contactId);
|
||||
groupId = db.getInboxGroup(contactId);
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
@@ -143,15 +142,16 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void displayHeaders(
|
||||
final Collection<PrivateMessageHeader> headers) {
|
||||
private void displayHeaders(final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
composeButton.setEnabled(true);
|
||||
adapter.clear();
|
||||
for(PrivateMessageHeader h : headers) adapter.add(h);
|
||||
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
||||
for(MessageHeader h : headers)
|
||||
adapter.add(new ConversationItem(h));
|
||||
adapter.sort(ConversationItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
selectFirstUnread();
|
||||
}
|
||||
@@ -161,7 +161,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
private void selectFirstUnread() {
|
||||
int firstUnread = -1, count = adapter.getCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
if(!adapter.getItem(i).isRead()) {
|
||||
if(!adapter.getItem(i).getHeader().isRead()) {
|
||||
firstUnread = i;
|
||||
break;
|
||||
}
|
||||
@@ -200,22 +200,21 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
} else if(e instanceof PrivateMessageAddedEvent) {
|
||||
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
|
||||
if(p.getContactId().equals(contactId)) {
|
||||
} else if(e instanceof MessageAddedEvent) {
|
||||
if(((MessageAddedEvent) e).getContactId().equals(contactId)) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
||||
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
|
||||
i.putExtra("net.sf.briar.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
|
||||
i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@@ -225,14 +224,15 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
||||
}
|
||||
|
||||
private void displayMessage(int position) {
|
||||
PrivateMessageHeader item = adapter.getItem(position);
|
||||
MessageHeader header = adapter.getItem(position).getHeader();
|
||||
Intent i = new Intent(this, ReadPrivateMessageActivity.class);
|
||||
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
|
||||
i.putExtra("net.sf.briar.CONTACT_NAME", contactName);
|
||||
i.putExtra("net.sf.briar.AUTHOR_NAME", item.getAuthor().getName());
|
||||
i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
|
||||
i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
|
||||
i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp());
|
||||
i.putExtra("net.sf.briar.GROUP_ID", header.getGroupId().getBytes());
|
||||
i.putExtra("net.sf.briar.AUTHOR_NAME", header.getAuthor().getName());
|
||||
i.putExtra("net.sf.briar.MESSAGE_ID", header.getId().getBytes());
|
||||
i.putExtra("net.sf.briar.CONTENT_TYPE", header.getContentType());
|
||||
i.putExtra("net.sf.briar.TIMESTAMP", header.getTimestamp());
|
||||
startActivityForResult(i, position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import static net.sf.briar.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -17,21 +17,21 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
|
||||
class ConversationAdapter extends ArrayAdapter<ConversationItem> {
|
||||
|
||||
ConversationAdapter(Context ctx) {
|
||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
||||
new ArrayList<PrivateMessageHeader>());
|
||||
new ArrayList<ConversationItem>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
PrivateMessageHeader item = getItem(position);
|
||||
MessageHeader header = getItem(position).getHeader();
|
||||
Context ctx = getContext();
|
||||
|
||||
LinearLayout layout = new LinearLayout(ctx);
|
||||
layout.setOrientation(HORIZONTAL);
|
||||
if(!item.isRead()) {
|
||||
if(!header.isRead()) {
|
||||
Resources res = ctx.getResources();
|
||||
layout.setBackgroundColor(res.getColor(R.color.unread_background));
|
||||
}
|
||||
@@ -42,13 +42,13 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
|
||||
name.setTextSize(18);
|
||||
name.setMaxLines(1);
|
||||
name.setPadding(10, 10, 10, 10);
|
||||
name.setText(item.getAuthor().getName());
|
||||
name.setText(header.getAuthor().getName());
|
||||
layout.addView(name);
|
||||
|
||||
TextView date = new TextView(ctx);
|
||||
date.setTextSize(14);
|
||||
date.setPadding(0, 10, 10, 10);
|
||||
long then = item.getTimestamp(), now = System.currentTimeMillis();
|
||||
long then = header.getTimestamp(), now = System.currentTimeMillis();
|
||||
date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
|
||||
layout.addView(date);
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.sf.briar.android.contact;
|
||||
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ConversationItem {
|
||||
|
||||
private final MessageHeader header;
|
||||
private boolean expanded;
|
||||
private byte[] body;
|
||||
|
||||
ConversationItem(MessageHeader header) {
|
||||
this.header = header;
|
||||
expanded = false;
|
||||
body = null;
|
||||
}
|
||||
|
||||
MessageHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
boolean isExpanded() {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
void setExpanded(boolean expanded) {
|
||||
this.expanded = expanded;
|
||||
}
|
||||
|
||||
byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.sf.briar.android.contact;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ConversationItemComparator
|
||||
implements Comparator<ConversationItem> {
|
||||
|
||||
public static final ConversationItemComparator INSTANCE =
|
||||
new ConversationItemComparator();
|
||||
|
||||
public int compare(ConversationItem a, ConversationItem b) {
|
||||
// The oldest message comes first
|
||||
long aTime = a.getHeader().getTimestamp();
|
||||
long bTime = b.getHeader().getTimestamp();
|
||||
if(aTime < bTime) return -1;
|
||||
if(aTime > bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NoSuchMessageException;
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.MessageId;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
@@ -60,6 +61,7 @@ implements OnClickListener {
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
private volatile MessageId messageId = null;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile long timestamp = -1;
|
||||
|
||||
@Override
|
||||
@@ -78,6 +80,9 @@ implements OnClickListener {
|
||||
byte[] b = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
messageId = new MessageId(b);
|
||||
b = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
|
||||
if(contentType == null) throw new IllegalStateException();
|
||||
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
|
||||
@@ -262,6 +267,7 @@ implements OnClickListener {
|
||||
} else if(view == replyButton) {
|
||||
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
||||
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
|
||||
i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
|
||||
i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
|
||||
i.putExtra("net.sf.briar.TIMESTAMP", timestamp);
|
||||
startActivity(i);
|
||||
|
||||
@@ -12,49 +12,46 @@ import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import net.sf.briar.android.invitation.AddContactActivity;
|
||||
import net.sf.briar.android.util.HorizontalSpace;
|
||||
import net.sf.briar.api.AuthorId;
|
||||
import net.sf.briar.api.Contact;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.LocalAuthor;
|
||||
import net.sf.briar.api.android.DatabaseUiExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NoSuchContactException;
|
||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.Message;
|
||||
import net.sf.briar.api.messaging.MessageFactory;
|
||||
import net.sf.briar.api.messaging.MessageId;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class WritePrivateMessageActivity extends RoboActivity
|
||||
implements OnItemSelectedListener, OnClickListener {
|
||||
implements OnClickListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WritePrivateMessageActivity.class.getName());
|
||||
|
||||
private TextView from = null;
|
||||
private ContactSpinnerAdapter adapter = null;
|
||||
private Spinner spinner = null;
|
||||
private TextView from = null, to = null;
|
||||
private ImageButton sendButton = null;
|
||||
private EditText content = null;
|
||||
|
||||
@@ -63,10 +60,13 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
@Inject private volatile MessageFactory messageFactory;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile ContactId contactId = null;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile MessageId parentId = null;
|
||||
private volatile long timestamp = -1;
|
||||
private volatile Contact contact = null;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Group group = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -74,16 +74,15 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
|
||||
Intent i = getIntent();
|
||||
int id = i.getIntExtra("net.sf.briar.CONTACT_ID", -1);
|
||||
if(id != -1) contactId = new ContactId(id);
|
||||
byte[] b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
|
||||
if(id == -1) throw new IllegalStateException();
|
||||
contactId = new ContactId(id);
|
||||
byte[] b = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
|
||||
if(b != null) parentId = new MessageId(b);
|
||||
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
|
||||
|
||||
if(state != null) {
|
||||
id = state.getInt("net.sf.briar.CONTACT_ID", -1);
|
||||
if(id != -1) contactId = new ContactId(id);
|
||||
}
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_WRAP);
|
||||
layout.setOrientation(VERTICAL);
|
||||
@@ -104,28 +103,16 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
sendButton = new ImageButton(this);
|
||||
sendButton.setBackgroundResource(0);
|
||||
sendButton.setImageResource(R.drawable.social_send_now);
|
||||
sendButton.setEnabled(false); // Enabled after loading the local author
|
||||
sendButton.setEnabled(false); // Enabled after loading the group
|
||||
sendButton.setOnClickListener(this);
|
||||
header.addView(sendButton);
|
||||
layout.addView(header);
|
||||
|
||||
header = new LinearLayout(this);
|
||||
header.setLayoutParams(MATCH_WRAP);
|
||||
header.setOrientation(HORIZONTAL);
|
||||
header.setGravity(CENTER_VERTICAL);
|
||||
|
||||
TextView to = new TextView(this);
|
||||
to = new TextView(this);
|
||||
to.setTextSize(18);
|
||||
to.setPadding(10, 0, 0, 10);
|
||||
to.setPadding(10, 10, 10, 10);
|
||||
to.setText(R.string.to);
|
||||
header.addView(to);
|
||||
|
||||
adapter = new ContactSpinnerAdapter(this);
|
||||
spinner = new Spinner(this);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setOnItemSelectedListener(this);
|
||||
header.addView(spinner);
|
||||
layout.addView(header);
|
||||
layout.addView(to);
|
||||
|
||||
content = new EditText(this);
|
||||
content.setId(1);
|
||||
@@ -140,20 +127,26 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadContacts();
|
||||
loadAuthorsAndGroup();
|
||||
}
|
||||
|
||||
private void loadContacts() {
|
||||
private void loadAuthorsAndGroup() {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<Contact> contacts = db.getContacts();
|
||||
contact = db.getContact(contactId);
|
||||
localAuthor = db.getLocalAuthor(contact.getLocalAuthorId());
|
||||
group = db.getGroup(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Loading contacts took " + duration + " ms");
|
||||
displayContacts(contacts);
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayAuthors();
|
||||
} catch(NoSuchContactException e) {
|
||||
finish();
|
||||
} catch(NoSuchSubscriptionException e) {
|
||||
finish();
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -165,101 +158,33 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void displayContacts(final Collection<Contact> contacts) {
|
||||
private void displayAuthors() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if(contacts.isEmpty()) finish();
|
||||
adapter.clear();
|
||||
for(Contact c : contacts) adapter.add(new ContactItem(c));
|
||||
adapter.sort(ContactItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
int count = adapter.getCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
ContactItem item = adapter.getItem(i);
|
||||
if(item == ContactItem.NEW) continue;
|
||||
if(item.getContact().getId().equals(contactId)) {
|
||||
spinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
if(contactId != null)
|
||||
state.putInt("net.sf.briar.CONTACT_ID", contactId.getInt());
|
||||
}
|
||||
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
ContactItem item = adapter.getItem(position);
|
||||
if(item == ContactItem.NEW) {
|
||||
contactId = null;
|
||||
localAuthor = null;
|
||||
startActivity(new Intent(this, AddContactActivity.class));
|
||||
} else {
|
||||
Contact c = item.getContact();
|
||||
contactId = c.getId();
|
||||
localAuthor = null;
|
||||
loadLocalAuthor(c.getLocalAuthorId());
|
||||
}
|
||||
sendButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void loadLocalAuthor(final AuthorId a) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
localAuthor = db.getLocalAuthor(a);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Loading author took " + duration + " ms");
|
||||
displayLocalAuthor();
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} catch(InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayLocalAuthor() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
String format = getResources().getString(R.string.format_from);
|
||||
from.setText(String.format(format, localAuthor.getName()));
|
||||
Resources res = getResources();
|
||||
String format = res.getString(R.string.format_from);
|
||||
String name = localAuthor.getName();
|
||||
from.setText(String.format(format, name));
|
||||
format = res.getString(R.string.format_to);
|
||||
name = contact.getAuthor().getName();
|
||||
to.setText(String.format(format, name));
|
||||
sendButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
contactId = null;
|
||||
sendButton.setEnabled(false);
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
if(localAuthor == null || contactId == null)
|
||||
if(contact == null || localAuthor == null)
|
||||
throw new IllegalStateException();
|
||||
try {
|
||||
byte[] b = content.getText().toString().getBytes("UTF-8");
|
||||
storeMessage(localAuthor, contactId, b);
|
||||
storeMessage(content.getText().toString().getBytes("UTF-8"));
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private void storeMessage(final LocalAuthor localAuthor,
|
||||
final ContactId contactId, final byte[] body) {
|
||||
private void storeMessage(final byte[] body) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
@@ -267,10 +192,10 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
// Don't use an earlier timestamp than the parent
|
||||
long time = System.currentTimeMillis();
|
||||
time = Math.max(time, timestamp + 1);
|
||||
Message m = messageFactory.createPrivateMessage(parentId,
|
||||
"text/plain", time, body);
|
||||
Message m = messageFactory.createAnonymousMessage(parentId,
|
||||
group, "text/plain", time, body);
|
||||
long now = System.currentTimeMillis();
|
||||
db.addLocalPrivateMessage(m, contactId);
|
||||
db.addLocalMessage(m);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
|
||||
@@ -77,7 +77,7 @@ SelectContactsDialog.Listener {
|
||||
setTitle(name);
|
||||
b = i.getByteArrayExtra("net.sf.briar.GROUP_SALT");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
group = new Group(id, name, b);
|
||||
group = new Group(id, name, b, false);
|
||||
subscribed = i.getBooleanExtra("net.sf.briar.SUBSCRIBED", false);
|
||||
boolean all = i.getBooleanExtra("net.sf.briar.VISIBLE_TO_ALL", false);
|
||||
|
||||
@@ -207,11 +207,11 @@ SelectContactsDialog.Listener {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
if(subscribe) {
|
||||
if(!wasSubscribed) db.subscribe(group);
|
||||
if(!wasSubscribed) db.addGroup(group);
|
||||
db.setVisibleToAll(group.getId(), all);
|
||||
if(!all) db.setVisibility(group.getId(), visible);
|
||||
} else if(wasSubscribed) {
|
||||
db.unsubscribe(group);
|
||||
db.removeGroup(group);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
|
||||
@@ -13,7 +13,6 @@ import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.WRAP_WRAP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -174,9 +173,9 @@ SelectContactsDialog.Listener {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
Group g = groupFactory.createGroup(name);
|
||||
Group g = groupFactory.createGroup(name, false);
|
||||
long now = System.currentTimeMillis();
|
||||
db.subscribe(g);
|
||||
db.addGroup(g);
|
||||
if(all) db.setVisibleToAll(g.getId(), true);
|
||||
else db.setVisibility(g.getId(), visible);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
@@ -189,8 +188,6 @@ SelectContactsDialog.Listener {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
|
||||
@@ -25,11 +25,11 @@ import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.android.DatabaseUiExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
|
||||
import net.sf.briar.api.lifecycle.LifecycleManager;
|
||||
@@ -116,8 +116,8 @@ OnClickListener, OnItemClickListener {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<GroupMessageHeader> headers =
|
||||
db.getGroupMessageHeaders(groupId);
|
||||
Collection<MessageHeader> headers =
|
||||
db.getMessageHeaders(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
@@ -141,13 +141,13 @@ OnClickListener, OnItemClickListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void displayHeaders(final Collection<GroupMessageHeader> headers) {
|
||||
private void displayHeaders(final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
adapter.clear();
|
||||
for(GroupMessageHeader h : headers) adapter.add(h);
|
||||
for(MessageHeader h : headers) adapter.add(h);
|
||||
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
selectFirstUnread();
|
||||
@@ -187,9 +187,8 @@ OnClickListener, OnItemClickListener {
|
||||
}
|
||||
|
||||
public void eventOccurred(DatabaseEvent e) {
|
||||
if(e instanceof GroupMessageAddedEvent) {
|
||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||
if(g.getGroup().getId().equals(groupId)) {
|
||||
if(e instanceof MessageAddedEvent) {
|
||||
if(((MessageAddedEvent) e).getGroup().getId().equals(groupId)) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
@@ -221,7 +220,7 @@ OnClickListener, OnItemClickListener {
|
||||
}
|
||||
|
||||
private void displayMessage(int position) {
|
||||
GroupMessageHeader item = adapter.getItem(position);
|
||||
MessageHeader item = adapter.getItem(position);
|
||||
Intent i = new Intent(this, ReadGroupPostActivity.class);
|
||||
i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
|
||||
i.putExtra("net.sf.briar.GROUP_NAME", groupName);
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.ArrayList;
|
||||
|
||||
import net.sf.briar.R;
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -18,16 +18,16 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
|
||||
class GroupAdapter extends ArrayAdapter<MessageHeader> {
|
||||
|
||||
GroupAdapter(Context ctx) {
|
||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
||||
new ArrayList<GroupMessageHeader>());
|
||||
new ArrayList<MessageHeader>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
GroupMessageHeader item = getItem(position);
|
||||
MessageHeader item = getItem(position);
|
||||
Context ctx = getContext();
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ import net.sf.briar.android.util.ListLoadingProgressBar;
|
||||
import net.sf.briar.api.android.DatabaseUiExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.SubscriptionAddedEvent;
|
||||
@@ -140,10 +140,11 @@ OnItemClickListener {
|
||||
long now = System.currentTimeMillis();
|
||||
for(GroupStatus s : db.getAvailableGroups()) {
|
||||
Group g = s.getGroup();
|
||||
if(g.isPrivate()) continue;
|
||||
if(s.isSubscribed()) {
|
||||
try {
|
||||
Collection<GroupMessageHeader> headers =
|
||||
db.getGroupMessageHeaders(g.getId());
|
||||
Collection<MessageHeader> headers =
|
||||
db.getMessageHeaders(g.getId());
|
||||
displayHeaders(g, headers);
|
||||
} catch(NoSuchSubscriptionException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
@@ -181,7 +182,7 @@ OnItemClickListener {
|
||||
}
|
||||
|
||||
private void displayHeaders(final Group g,
|
||||
final Collection<GroupMessageHeader> headers) {
|
||||
final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
@@ -240,9 +241,12 @@ OnItemClickListener {
|
||||
}
|
||||
|
||||
public void eventOccurred(DatabaseEvent e) {
|
||||
if(e instanceof GroupMessageAddedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
loadHeaders(((GroupMessageAddedEvent) e).getGroup());
|
||||
if(e instanceof MessageAddedEvent) {
|
||||
Group g = ((MessageAddedEvent) e).getGroup();
|
||||
if(!g.isPrivate()) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
loadHeaders(g);
|
||||
}
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
@@ -254,9 +258,12 @@ OnItemClickListener {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
|
||||
loadHeaders();
|
||||
} else if(e instanceof SubscriptionRemovedEvent) {
|
||||
// Reload the group, expecting NoSuchSubscriptionException
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
|
||||
loadHeaders(((SubscriptionRemovedEvent) e).getGroup());
|
||||
Group g = ((SubscriptionRemovedEvent) e).getGroup();
|
||||
if(!g.isPrivate()) {
|
||||
// Reload the group, expecting NoSuchSubscriptionException
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
|
||||
loadHeaders(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,8 +273,8 @@ OnItemClickListener {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<GroupMessageHeader> headers =
|
||||
db.getGroupMessageHeaders(g.getId());
|
||||
Collection<MessageHeader> headers =
|
||||
db.getMessageHeaders(g.getId());
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Partial load took " + duration + " ms");
|
||||
@@ -307,8 +314,10 @@ OnItemClickListener {
|
||||
lifecycleManager.waitForDatabase();
|
||||
int available = 0;
|
||||
long now = System.currentTimeMillis();
|
||||
for(GroupStatus s : db.getAvailableGroups())
|
||||
if(!s.isSubscribed()) available++;
|
||||
for(GroupStatus s : db.getAvailableGroups()) {
|
||||
if(!s.isSubscribed() && !s.getGroup().isPrivate())
|
||||
available++;
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Loading available took " + duration + " ms");
|
||||
@@ -354,9 +363,9 @@ OnItemClickListener {
|
||||
startActivity(new Intent(this, ManageGroupsActivity.class));
|
||||
} else {
|
||||
Intent i = new Intent(this, GroupActivity.class);
|
||||
i.putExtra("net.sf.briar.GROUP_ID",
|
||||
item.getGroup().getId().getBytes());
|
||||
i.putExtra("net.sf.briar.GROUP_NAME", item.getGroup().getName());
|
||||
Group g = item.getGroup();
|
||||
i.putExtra("net.sf.briar.GROUP_ID", g.getId().getBytes());
|
||||
i.putExtra("net.sf.briar.GROUP_NAME", g.getName());
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package net.sf.briar.android.groups;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.briar.android.DescendingHeaderComparator;
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
|
||||
class GroupListItem {
|
||||
|
||||
static final GroupListItem MANAGE = new GroupListItem(null,
|
||||
Collections.<GroupMessageHeader>emptyList());
|
||||
Collections.<MessageHeader>emptyList());
|
||||
|
||||
private final Group group;
|
||||
private final boolean empty;
|
||||
@@ -21,7 +18,7 @@ class GroupListItem {
|
||||
private final long timestamp;
|
||||
private final int unread;
|
||||
|
||||
GroupListItem(Group group, Collection<GroupMessageHeader> headers) {
|
||||
GroupListItem(Group group, Collection<MessageHeader> headers) {
|
||||
this.group = group;
|
||||
empty = headers.isEmpty();
|
||||
if(empty) {
|
||||
@@ -30,17 +27,21 @@ class GroupListItem {
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
} else {
|
||||
List<GroupMessageHeader> list =
|
||||
new ArrayList<GroupMessageHeader>(headers);
|
||||
Collections.sort(list, DescendingHeaderComparator.INSTANCE);
|
||||
GroupMessageHeader newest = list.get(0);
|
||||
MessageHeader newest = null;
|
||||
long timestamp = 0;
|
||||
int unread = 0;
|
||||
for(MessageHeader h : headers) {
|
||||
if(h.getTimestamp() > timestamp) {
|
||||
timestamp = h.getTimestamp();
|
||||
newest = h;
|
||||
}
|
||||
if(!h.isRead()) unread++;
|
||||
}
|
||||
Author a = newest.getAuthor();
|
||||
if(a == null) authorName = null;
|
||||
else authorName = a.getName();
|
||||
contentType = newest.getContentType();
|
||||
timestamp = newest.getTimestamp();
|
||||
int unread = 0;
|
||||
for(GroupMessageHeader h : list) if(!h.isRead()) unread++;
|
||||
this.timestamp = newest.getTimestamp();
|
||||
this.unread = unread;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.android.groups.ManageGroupsItem.NONE;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -66,20 +67,23 @@ implements DatabaseListener, OnItemClickListener {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
db.addListener(this);
|
||||
loadAvailableGroups();
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
private void loadAvailableGroups() {
|
||||
private void loadGroups() {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<GroupStatus> available = db.getAvailableGroups();
|
||||
Collection<GroupStatus> available =
|
||||
new ArrayList<GroupStatus>();
|
||||
for(GroupStatus s : db.getAvailableGroups())
|
||||
if(!s.getGroup().isPrivate()) available.add(s);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayAvailableGroups(available);
|
||||
displayGroups(available);
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -92,14 +96,13 @@ implements DatabaseListener, OnItemClickListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void displayAvailableGroups(
|
||||
final Collection<GroupStatus> available) {
|
||||
private void displayGroups(final Collection<GroupStatus> available) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
setContentView(list);
|
||||
adapter.clear();
|
||||
for(GroupStatus g : available)
|
||||
adapter.add(new ManageGroupsItem(g));
|
||||
for(GroupStatus s : available)
|
||||
adapter.add(new ManageGroupsItem(s));
|
||||
adapter.sort(ItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
@@ -116,13 +119,13 @@ implements DatabaseListener, OnItemClickListener {
|
||||
if(e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Remote subscriptions changed, reloading");
|
||||
loadAvailableGroups();
|
||||
loadGroups();
|
||||
} else if(e instanceof SubscriptionAddedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
|
||||
loadAvailableGroups();
|
||||
loadGroups();
|
||||
} else if(e instanceof SubscriptionRemovedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
|
||||
loadAvailableGroups();
|
||||
loadGroups();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
@@ -71,10 +72,10 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Group group = null;
|
||||
private volatile MessageId parentId = null;
|
||||
private volatile long timestamp = -1;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Group group = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -213,7 +214,9 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<Group> groups = db.getSubscriptions();
|
||||
Collection<Group> groups = new ArrayList<Group>();
|
||||
for(Group g : db.getGroups())
|
||||
if(!g.isPrivate()) groups.add(g);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Loading groups took " + duration + " ms");
|
||||
@@ -341,7 +344,7 @@ implements OnItemSelectedListener, OnClickListener {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
db.addLocalGroupMessage(m);
|
||||
db.addLocalMessage(m);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
|
||||
@@ -13,7 +13,6 @@ import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static net.sf.briar.android.util.CommonLayoutParams.WRAP_WRAP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -123,13 +122,8 @@ implements OnEditorActionListener, OnClickListener {
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
final byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
final byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||
LocalAuthor a;
|
||||
try {
|
||||
a = authorFactory.createLocalAuthor(nickname, publicKey,
|
||||
privateKey);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
LocalAuthor a = authorFactory.createLocalAuthor(nickname,
|
||||
publicKey, privateKey);
|
||||
storeLocalAuthor(a);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package net.sf.briar.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface AuthorFactory {
|
||||
|
||||
Author createAuthor(String name, byte[] publicKey) throws IOException;
|
||||
Author createAuthor(String name, byte[] publicKey);
|
||||
|
||||
LocalAuthor createLocalAuthor(String name, byte[] publicKey,
|
||||
byte[] privateKey) throws IOException;
|
||||
byte[] privateKey);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ public interface CryptoComponent {
|
||||
byte[] deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||
boolean alice) throws GeneralSecurityException;
|
||||
|
||||
/** Derives a group salt from the given master secret. */
|
||||
byte[] deriveGroupSalt(byte[] secret);
|
||||
|
||||
/**
|
||||
* Derives an initial secret for the given transport from the given master
|
||||
* secret.
|
||||
|
||||
@@ -48,22 +48,25 @@ public interface DatabaseComponent {
|
||||
void removeListener(DatabaseListener d);
|
||||
|
||||
/**
|
||||
* Stores a contact with the given pseudonym, associated with the given
|
||||
* local pseudonym, and returns an ID for the contact.
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* and returns an ID for the contact.
|
||||
*/
|
||||
ContactId addContact(Author remote, AuthorId local) throws DbException;
|
||||
|
||||
/** Stores an endpoint. */
|
||||
void addEndpoint(Endpoint ep) throws DbException;
|
||||
|
||||
/**
|
||||
* Subscribes to a group, or returns false if the user already has the
|
||||
* maximum number of public subscriptions.
|
||||
*/
|
||||
boolean addGroup(Group g) throws DbException;
|
||||
|
||||
/** Stores a local pseudonym. */
|
||||
void addLocalAuthor(LocalAuthor a) throws DbException;
|
||||
|
||||
/** Stores a locally generated group message. */
|
||||
void addLocalGroupMessage(Message m) throws DbException;
|
||||
|
||||
/** Stores a locally generated private message. */
|
||||
void addLocalPrivateMessage(Message m, ContactId c) throws DbException;
|
||||
/** Stores a local message. */
|
||||
void addLocalMessage(Message m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given temporary secrets and deletes any secrets that have
|
||||
@@ -77,14 +80,17 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
boolean addTransport(TransportId t, long maxLatency) throws DbException;
|
||||
|
||||
/** Returns true if any messages are sendable to the given contact. */
|
||||
boolean containsSendableMessages(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates an acknowledgement for the given contact, or returns null if
|
||||
* there are no messages to acknowledge.
|
||||
* Returns an acknowledgement for the given contact, or null if there are
|
||||
* no messages to acknowledge.
|
||||
*/
|
||||
Ack generateAck(ContactId c, int maxMessages) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a batch of raw messages for the given contact, with a total
|
||||
* Returns a batch of raw messages for the given contact, with a total
|
||||
* length less than or equal to the given length, for transmission over a
|
||||
* transport with the given maximum latency. Returns null if there are no
|
||||
* sendable messages that fit in the given length.
|
||||
@@ -93,7 +99,7 @@ public interface DatabaseComponent {
|
||||
long maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a batch of raw messages for the given contact from the given
|
||||
* Returns a batch of raw messages for the given contact from the given
|
||||
* collection of requested messages, with a total length less than or equal
|
||||
* to the given length, for transmission over a transport with the given
|
||||
* maximum latency. Any messages that were either added to the batch, or
|
||||
@@ -106,19 +112,19 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Generates an offer for the given contact, or returns null if there are
|
||||
* no messages to offer.
|
||||
* Returns an offer for the given contact, or null if there are no messages
|
||||
* to offer.
|
||||
*/
|
||||
Offer generateOffer(ContactId c, int maxMessages) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a retention ack for the given contact, or returns null if no
|
||||
* Returns a retention ack for the given contact, or null if no retention
|
||||
* ack is due.
|
||||
*/
|
||||
RetentionAck generateRetentionAck(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a retention update for the given contact, for transmission
|
||||
* Returns a retention update for the given contact, for transmission
|
||||
* over a transport with the given latency. Returns null if no update is
|
||||
* due.
|
||||
*/
|
||||
@@ -126,13 +132,13 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a subscription ack for the given contact, or returns null if
|
||||
* no ack is due.
|
||||
* Returns a subscription ack for the given contact, or null if no
|
||||
* subscription ack is due.
|
||||
*/
|
||||
SubscriptionAck generateSubscriptionAck(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a subscription update for the given contact, for transmission
|
||||
* Returns a subscription update for the given contact, for transmission
|
||||
* over a transport with the given latency. Returns null if no update is
|
||||
* due.
|
||||
*/
|
||||
@@ -140,14 +146,14 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a batch of transport acks for the given contact, or returns
|
||||
* null if no acks are due.
|
||||
* Returns a batch of transport acks for the given contact, or null if no
|
||||
* transport acks are due.
|
||||
*/
|
||||
Collection<TransportAck> generateTransportAcks(ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Generates a batch of transport updates for the given contact, for
|
||||
* Returns a batch of transport updates for the given contact, for
|
||||
* transmission over a transport with the given latency. Returns null if no
|
||||
* updates are due.
|
||||
*/
|
||||
@@ -169,8 +175,20 @@ public interface DatabaseComponent {
|
||||
/** Returns the group with the given ID, if the user subscribes to it. */
|
||||
Group getGroup(GroupId g) throws DbException;
|
||||
|
||||
/** Returns the headers of all messages in the given group. */
|
||||
Collection<GroupMessageHeader> getGroupMessageHeaders(GroupId g)
|
||||
/** Returns all groups to which the user subscribes. */
|
||||
Collection<Group> getGroups() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the inbox group for the given contact, or null if no
|
||||
* inbox group has been set.
|
||||
*/
|
||||
GroupId getInboxGroup(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all messages in the inbox group for the given
|
||||
* contact, or null if no inbox group has been set.
|
||||
*/
|
||||
Collection<MessageHeader> getInboxMessageHeaders(ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -195,11 +213,8 @@ public interface DatabaseComponent {
|
||||
/** Returns the body of the message with the given ID. */
|
||||
byte[] getMessageBody(MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all private messages to or from the given
|
||||
* contact.
|
||||
*/
|
||||
Collection<PrivateMessageHeader> getPrivateMessageHeaders(ContactId c)
|
||||
/** Returns the headers of all messages in the given group. */
|
||||
Collection<MessageHeader> getMessageHeaders(GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/** Returns true if the given message has been read. */
|
||||
@@ -212,24 +227,15 @@ public interface DatabaseComponent {
|
||||
/** Returns all temporary secrets. */
|
||||
Collection<TemporarySecret> getSecrets() throws DbException;
|
||||
|
||||
/** Returns the set of groups to which the user subscribes. */
|
||||
Collection<Group> getSubscriptions() throws DbException;
|
||||
|
||||
/** Returns the maximum latencies of all local transports. */
|
||||
Map<TransportId, Long> getTransportLatencies() throws DbException;
|
||||
|
||||
/** Returns the number of unread messages in each subscribed group. */
|
||||
Map<GroupId, Integer> getUnreadMessageCounts() throws DbException;
|
||||
|
||||
/** Returns the contacts to which the given group is visible. */
|
||||
/** Returns the IDs of all contacts to which the given group is visible. */
|
||||
Collection<ContactId> getVisibility(GroupId g) throws DbException;
|
||||
|
||||
/** Returns the subscriptions that are visible to the given contact. */
|
||||
Collection<GroupId> getVisibleSubscriptions(ContactId c) throws DbException;
|
||||
|
||||
/** Returns true if any messages are sendable to the given contact. */
|
||||
boolean hasSendableMessages(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing connection counter for the given endpoint
|
||||
* in the given rotation period and returns the old value, or -1 if the
|
||||
@@ -295,6 +301,12 @@ public interface DatabaseComponent {
|
||||
/** Removes a contact (and all associated state) from the database. */
|
||||
void removeContact(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Unsubscribes from a group. Any messages belonging to the group
|
||||
* are deleted from the database.
|
||||
*/
|
||||
void removeGroup(Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a local pseudonym (and all associated state) from the database.
|
||||
*/
|
||||
@@ -314,8 +326,14 @@ public interface DatabaseComponent {
|
||||
long centre, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message read or unread and returns true if it was
|
||||
* previously read.
|
||||
* Makes a private group visible to the given contact, adds it to the
|
||||
* contact's subscriptions, and sets it as the inbox group for the contact.
|
||||
*/
|
||||
public void setInboxGroup(ContactId c, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks a message read or unread and returns true if it was previously
|
||||
* read.
|
||||
*/
|
||||
boolean setReadFlag(MessageId m, boolean read) throws DbException;
|
||||
|
||||
@@ -342,16 +360,4 @@ public interface DatabaseComponent {
|
||||
* current contacts.
|
||||
*/
|
||||
void setVisibleToAll(GroupId g, boolean all) throws DbException;
|
||||
|
||||
/**
|
||||
* Subscribes to the given group, or returns false if the user already has
|
||||
* the maximum number of subscriptions.
|
||||
*/
|
||||
boolean subscribe(Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Unsubscribes from the given group. Any messages belonging to the group
|
||||
* are deleted from the database.
|
||||
*/
|
||||
void unsubscribe(Group g) throws DbException;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.MessageId;
|
||||
|
||||
public class GroupMessageHeader extends MessageHeader {
|
||||
|
||||
private final GroupId groupId;
|
||||
|
||||
public GroupMessageHeader(MessageId id, MessageId parent, Author author,
|
||||
String contentType, long timestamp, boolean read,
|
||||
GroupId groupId) {
|
||||
super(id, parent, author, contentType, timestamp, read);
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/** Returns the ID of the group to which the message belongs. */
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.MessageId;
|
||||
|
||||
public abstract class MessageHeader {
|
||||
public class MessageHeader {
|
||||
|
||||
private final MessageId id, parent;
|
||||
private final GroupId groupId;
|
||||
private final Author author;
|
||||
private final String contentType;
|
||||
private final long timestamp;
|
||||
private final boolean read;
|
||||
|
||||
protected MessageHeader(MessageId id, MessageId parent, Author author,
|
||||
String contentType, long timestamp, boolean read) {
|
||||
public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
|
||||
Author author, String contentType, long timestamp, boolean read) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.groupId = groupId;
|
||||
this.author = author;
|
||||
this.contentType = contentType;
|
||||
this.timestamp = timestamp;
|
||||
@@ -34,6 +37,13 @@ public abstract class MessageHeader {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the group to which the message belongs.
|
||||
*/
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message's author, or null if this is an anonymous message.
|
||||
*/
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.messaging.MessageId;
|
||||
|
||||
public class PrivateMessageHeader extends MessageHeader {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final boolean incoming;
|
||||
|
||||
public PrivateMessageHeader(MessageId id, MessageId parent, Author author,
|
||||
String contentType, long timestamp, boolean read,
|
||||
ContactId contactId, boolean incoming) {
|
||||
super(id, parent, author, contentType, timestamp, read);
|
||||
this.contactId = contactId;
|
||||
this.incoming = incoming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the contact who is the sender (if incoming) or
|
||||
* recipient (if outgoing) of this message.
|
||||
*/
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
/** Returns true if this is an incoming message. */
|
||||
public boolean isIncoming() {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package net.sf.briar.api.db.event;
|
||||
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
|
||||
/** An event that is broadcast when a group message is added to the database. */
|
||||
public class GroupMessageAddedEvent extends DatabaseEvent {
|
||||
|
||||
private final Group group;
|
||||
private final boolean incoming;
|
||||
|
||||
public GroupMessageAddedEvent(Group group, boolean incoming) {
|
||||
this.group = group;
|
||||
this.incoming = incoming;
|
||||
}
|
||||
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public boolean isIncoming() {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package net.sf.briar.api.db.event;
|
||||
|
||||
import net.sf.briar.api.AuthorId;
|
||||
|
||||
/** An event that is broadcast when a pseudonym for the user is added. */
|
||||
/** An event that is broadcast when a local pseudonym is added. */
|
||||
public class LocalAuthorAddedEvent extends DatabaseEvent {
|
||||
|
||||
private final AuthorId authorId;
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.sf.briar.api.db.event;
|
||||
|
||||
import net.sf.briar.api.AuthorId;
|
||||
|
||||
/** An event that is broadcast when a pseudonym for the user is removed. */
|
||||
/** An event that is broadcast when a local pseudonym is removed. */
|
||||
public class LocalAuthorRemovedEvent extends DatabaseEvent {
|
||||
|
||||
private final AuthorId authorId;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.sf.briar.api.db.event;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
|
||||
/** An event that is broadcast when a message is added to the database. */
|
||||
public class MessageAddedEvent extends DatabaseEvent {
|
||||
|
||||
private final Group group;
|
||||
private final ContactId contactId;
|
||||
|
||||
public MessageAddedEvent(Group group, ContactId contactId) {
|
||||
this.group = group;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
/** Returns the group to which the message belongs. */
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the contact from which the message was received, or
|
||||
* null if the message was locally generated.
|
||||
*/
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package net.sf.briar.api.db.event;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a private message is added to the database.
|
||||
*/
|
||||
public class PrivateMessageAddedEvent extends DatabaseEvent {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final boolean incoming;
|
||||
|
||||
public PrivateMessageAddedEvent(ContactId contactId, boolean incoming) {
|
||||
this.contactId = contactId;
|
||||
this.incoming = incoming;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public boolean isIncoming() {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ public class Group {
|
||||
private final GroupId id;
|
||||
private final String name;
|
||||
private final byte[] salt;
|
||||
private final boolean isPrivate;
|
||||
|
||||
public Group(GroupId id, String name, byte[] salt) {
|
||||
public Group(GroupId id, String name, byte[] salt, boolean isPrivate) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.salt = salt;
|
||||
this.isPrivate = isPrivate;
|
||||
}
|
||||
|
||||
/** Returns the group's unique identifier. */
|
||||
@@ -31,6 +33,11 @@ public class Group {
|
||||
return salt;
|
||||
}
|
||||
|
||||
/** Returns true if the group is private. */
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package net.sf.briar.api.messaging;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface GroupFactory {
|
||||
|
||||
/** Creates a group with the given name and a random salt. */
|
||||
Group createGroup(String name) throws IOException;
|
||||
Group createGroup(String name, boolean isPrivate);
|
||||
|
||||
/** Creates a group with the given name and salt. */
|
||||
Group createGroup(String name, byte[] salt) throws IOException;
|
||||
Group createGroup(String name, byte[] salt, boolean isPrivate);
|
||||
}
|
||||
|
||||
@@ -8,17 +8,10 @@ import net.sf.briar.api.crypto.PrivateKey;
|
||||
|
||||
public interface MessageFactory {
|
||||
|
||||
/** Creates a private message. */
|
||||
Message createPrivateMessage(MessageId parent, String contentType,
|
||||
long timestamp, byte[] body) throws IOException,
|
||||
GeneralSecurityException;
|
||||
|
||||
/** Creates an anonymous group message. */
|
||||
Message createAnonymousMessage(MessageId parent, Group group,
|
||||
String contentType, long timestamp, byte[] body) throws IOException,
|
||||
GeneralSecurityException;
|
||||
|
||||
/** Creates a pseudonymous group message. */
|
||||
Message createPseudonymousMessage(MessageId parent, Group group,
|
||||
Author author, PrivateKey privateKey, String contentType,
|
||||
long timestamp, byte[] body) throws IOException,
|
||||
|
||||
@@ -11,7 +11,7 @@ public interface MessagingConstants {
|
||||
*/
|
||||
int MAX_PACKET_LENGTH = MIN_CONNECTION_LENGTH / 2;
|
||||
|
||||
/** The maximum number of groups a user may subscribe to. */
|
||||
/** The maximum number of public groups a user may subscribe to. */
|
||||
int MAX_SUBSCRIPTIONS = 3000;
|
||||
|
||||
/** The maximum length of a group's name in UTF-8 bytes. */
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.Collection;
|
||||
/** A packet updating the recipient's view of the sender's subscriptions. */
|
||||
public class SubscriptionUpdate {
|
||||
|
||||
private final Collection<Group> subs;
|
||||
private final Collection<Group> groups;
|
||||
private final long version;
|
||||
|
||||
public SubscriptionUpdate(Collection<Group> subs, long version) {
|
||||
this.subs = subs;
|
||||
public SubscriptionUpdate(Collection<Group> groups, long version) {
|
||||
this.groups = groups;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class SubscriptionUpdate {
|
||||
* has made visible to the recipient.
|
||||
*/
|
||||
public Collection<Group> getGroups() {
|
||||
return subs;
|
||||
return groups;
|
||||
}
|
||||
|
||||
/** Returns the update's version number. */
|
||||
|
||||
@@ -8,19 +8,18 @@ public class UnverifiedMessage {
|
||||
private final MessageId parent;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
private final String contentType, subject;
|
||||
private final String contentType;
|
||||
private final long timestamp;
|
||||
private final byte[] raw, signature;
|
||||
private final int bodyStart, bodyLength, signedLength;
|
||||
|
||||
public UnverifiedMessage(MessageId parent, Group group, Author author,
|
||||
String contentType, String subject, long timestamp, byte[] raw,
|
||||
byte[] signature, int bodyStart, int bodyLength, int signedLength) {
|
||||
String contentType, long timestamp, byte[] raw, byte[] signature,
|
||||
int bodyStart, int bodyLength, int signedLength) {
|
||||
this.parent = parent;
|
||||
this.group = group;
|
||||
this.author = author;
|
||||
this.contentType = contentType;
|
||||
this.subject = subject;
|
||||
this.timestamp = timestamp;
|
||||
this.raw = raw;
|
||||
this.signature = signature;
|
||||
@@ -58,15 +57,6 @@ public class UnverifiedMessage {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message's subject line, which is created from the first 50
|
||||
* bytes of the message body if the content type is text/plain, or is the
|
||||
* empty string otherwise.
|
||||
*/
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
/** Returns the message's timestamp. */
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
|
||||
@@ -64,6 +64,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
// Labels for secret derivation
|
||||
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
|
||||
private static final byte[] SALT = { 'S', 'A', 'L', 'T', '\0' };
|
||||
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
|
||||
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
|
||||
// Label for confirmation code derivation
|
||||
@@ -235,6 +236,14 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return agreement.calculateAgreement(ecPub).toByteArray();
|
||||
}
|
||||
|
||||
public byte[] deriveGroupSalt(byte[] secret) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
return counterModeKdf(secret, SALT, 0);
|
||||
}
|
||||
|
||||
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
@@ -13,8 +13,7 @@ import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.GroupStatus;
|
||||
@@ -79,8 +78,8 @@ interface Database<T> {
|
||||
void commitTransaction(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact with the given pseudonym, associated with the given
|
||||
* local pseudonym, and returns an ID for the contact.
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* and returns an ID for the contact.
|
||||
* <p>
|
||||
* Locking: contact write, message write, retention write,
|
||||
* subscription write, transport write, window write.
|
||||
@@ -96,13 +95,12 @@ interface Database<T> {
|
||||
void addEndpoint(T txn, Endpoint ep) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given message, or returns false if the message is already in
|
||||
* the database.
|
||||
* Subscribes to a group, or returns false if the user already has the
|
||||
* maximum number of subscriptions.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
* Locking: message write, subscription write.
|
||||
*/
|
||||
boolean addGroupMessage(T txn, Message m, boolean incoming)
|
||||
throws DbException;
|
||||
boolean addGroup(T txn, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a local pseudonym.
|
||||
@@ -112,6 +110,13 @@ interface Database<T> {
|
||||
*/
|
||||
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a message.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
*/
|
||||
void addMessage(T txn, Message m, boolean incoming) throws DbException;
|
||||
|
||||
/**
|
||||
* Records a received message as needing to be acknowledged.
|
||||
* <p>
|
||||
@@ -119,15 +124,6 @@ interface Database<T> {
|
||||
*/
|
||||
void addMessageToAck(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given message, or returns false if the message is already in
|
||||
* the database.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
*/
|
||||
boolean addPrivateMessage(T txn, Message m, ContactId c, boolean incoming)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given temporary secrets and deletes any secrets that have
|
||||
* been made obsolete.
|
||||
@@ -146,14 +142,6 @@ interface Database<T> {
|
||||
void addStatus(T txn, ContactId c, MessageId m, boolean seen)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Subscribes to the given group, or returns false if the user already has
|
||||
* the maximum number of subscriptions.
|
||||
* <p>
|
||||
* Locking: subscription write.
|
||||
*/
|
||||
boolean addSubscription(T txn, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport and returns true if the transport was not previously
|
||||
* in the database.
|
||||
@@ -184,6 +172,13 @@ interface Database<T> {
|
||||
*/
|
||||
boolean containsContact(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the user subscribes to the given group.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
boolean containsGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given local pseudonym.
|
||||
* <p>
|
||||
@@ -199,11 +194,11 @@ interface Database<T> {
|
||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the user subscribes to the given group.
|
||||
* Returns true if any messages are sendable to the given contact.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
* Locking: message read, subscription read.
|
||||
*/
|
||||
boolean containsSubscription(T txn, GroupId g) throws DbException;
|
||||
boolean containsSendableMessages(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given transport.
|
||||
@@ -213,12 +208,12 @@ interface Database<T> {
|
||||
boolean containsTransport(T txn, TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the user subscribes to the given group and the
|
||||
* subscription is visible to the given contact.
|
||||
* Returns true if the user subscribes to the given group and the group is
|
||||
* visible to the given contact.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
boolean containsVisibleSubscription(T txn, ContactId c, GroupId g)
|
||||
boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -279,25 +274,34 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Returns the group with the given ID, if the user subscribes to it.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Group getGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all messages in the given group.
|
||||
* Returns all groups to which the user subscribes.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Collection<GroupMessageHeader> getGroupMessageHeaders(T txn, GroupId g)
|
||||
throws DbException;
|
||||
Collection<Group> getGroups(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the parent of the given group message, or null if either the
|
||||
* message has no parent, or the parent is absent from the database, or the
|
||||
* parent belongs to a different group.
|
||||
* Returns the ID of the inbox group for the given contact, or null if no
|
||||
* inbox group has been set.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
* Locking: contact read, subscription read.
|
||||
*/
|
||||
MessageId getGroupMessageParent(T txn, MessageId m) throws DbException;
|
||||
GroupId getInboxGroup(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all messages in the inbox group for the given
|
||||
* contact, or null if no inbox group has been set.
|
||||
* <p>
|
||||
* Locking: contact read, identity read, message read, subscription read.
|
||||
*/
|
||||
Collection<MessageHeader> getInboxMessageHeaders(T txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the time at which a connection to each contact was last opened
|
||||
@@ -345,17 +349,16 @@ interface Database<T> {
|
||||
byte[] getMessageBody(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all private messages to or from the given
|
||||
* contact.
|
||||
* Returns the headers of all messages in the given group.
|
||||
* <p>
|
||||
* Locking: contact read, identity read, message read.
|
||||
* Locking: message read.
|
||||
*/
|
||||
Collection<PrivateMessageHeader> getPrivateMessageHeaders(T txn,
|
||||
ContactId c) throws DbException;
|
||||
Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
|
||||
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.
|
||||
* Returns the IDs of messages received from the given contact that need
|
||||
* to be acknowledged, up to the given number of messages.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
*/
|
||||
@@ -371,6 +374,23 @@ interface Database<T> {
|
||||
Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
|
||||
int maxMessages) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of the oldest messages in the database, with a total
|
||||
* size less than or equal to the given size.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
*/
|
||||
Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the parent of the given message, or null if either the message
|
||||
* has no parent, or the parent is absent from the database, or the parent
|
||||
* belongs to a different group.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
*/
|
||||
MessageId getParent(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the message identified by the given ID, in serialised form.
|
||||
* <p>
|
||||
@@ -388,14 +408,6 @@ interface Database<T> {
|
||||
byte[] getRawMessageIfSendable(T txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of the oldest messages in the database, with a total
|
||||
* size less than or equal to the given size.
|
||||
* <p>
|
||||
* Locking: message read.
|
||||
*/
|
||||
Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the given message has been read.
|
||||
* <p>
|
||||
@@ -444,20 +456,6 @@ interface Database<T> {
|
||||
Collection<MessageId> getSendableMessages(T txn, ContactId c, int maxLength)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the groups to which the user subscribes.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Collection<Group> getSubscriptions(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the groups to which the given contact subscribes.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a subscription ack for the given contact, or null if no ack is
|
||||
* due.
|
||||
@@ -518,27 +516,21 @@ interface Database<T> {
|
||||
Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contacts to which the given group is visible.
|
||||
* Returns the IDs of all contacts to which the given group is visible.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the subscriptions that are visible to the given contact.
|
||||
* Returns the IDs of all private groups that are visible to the given
|
||||
* contact.
|
||||
* <p>
|
||||
* Locking: subscription read.
|
||||
*/
|
||||
Collection<GroupId> getVisibleSubscriptions(T txn, ContactId c)
|
||||
Collection<GroupId> getVisiblePrivateGroups(T txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if any messages are sendable to the given contact.
|
||||
* <p>
|
||||
* Locking: message read, subscription read.
|
||||
*/
|
||||
boolean hasSendableMessages(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing connection counter for the given endpoint
|
||||
* in the given rotation period and returns the old value, or -1 if the
|
||||
@@ -576,7 +568,7 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a contact (and all associated state) from the database.
|
||||
* Removes a contact from the database.
|
||||
* <p>
|
||||
* Locking: contact write, message write, retention write,
|
||||
* subscription write, transport write, window write.
|
||||
@@ -584,8 +576,16 @@ interface Database<T> {
|
||||
void removeContact(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the local pseudonym with the given ID (and all associated
|
||||
* state) from the database.
|
||||
* Unsubscribes from a group. Any messages belonging to the group are
|
||||
* deleted from the database.
|
||||
* <p>
|
||||
* Locking: message write, subscription write.
|
||||
*/
|
||||
void removeGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a local pseudonym (and all associated contacts) from the
|
||||
* database.
|
||||
* <p>
|
||||
* Locking: contact write, identity write, message write, retention write,
|
||||
* subscription write, transport write, window write.
|
||||
@@ -608,23 +608,6 @@ interface Database<T> {
|
||||
void removeMessagesToAck(T txn, ContactId c, Collection<MessageId> acked)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks any of the given messages that are considered outstanding with
|
||||
* respect to the given contact as seen by the contact.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
*/
|
||||
void removeOutstandingMessages(T txn, ContactId c,
|
||||
Collection<MessageId> acked) throws DbException;
|
||||
|
||||
/**
|
||||
* Unsubscribes from the given group. Any messages belonging to the group
|
||||
* are deleted from the database.
|
||||
* <p>
|
||||
* Locking: message write, subscription write.
|
||||
*/
|
||||
void removeSubscription(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
* <p>
|
||||
@@ -648,6 +631,24 @@ interface Database<T> {
|
||||
void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
|
||||
long centre, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the groups to which the given contact subscribes and returns
|
||||
* true, unless an update with an equal or higher version number has
|
||||
* already been received from the contact.
|
||||
* <p>
|
||||
* Locking: subscription write.
|
||||
*/
|
||||
boolean setGroups(T txn, ContactId c, Collection<Group> groups,
|
||||
long version) throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a private group visible to the given contact, adds it to the
|
||||
* contact's subscriptions, and sets it as the inbox group for the contact.
|
||||
* <p>
|
||||
* Locking: contact read, message write, subscription write.
|
||||
*/
|
||||
public void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the time at which a connection to the given contact was last made.
|
||||
* <p>
|
||||
@@ -656,8 +657,8 @@ interface Database<T> {
|
||||
void setLastConnected(T txn, ContactId c, long now) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message read or unread and returns true if it was
|
||||
* previously read.
|
||||
* Marks a message read or unread and returns true if it was previously
|
||||
* read.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
*/
|
||||
@@ -703,16 +704,6 @@ interface Database<T> {
|
||||
boolean setStatusSeenIfVisible(T txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the groups to which the given contact subscribes and returns
|
||||
* true, unless an update with an equal or higher version number has
|
||||
* already been received from the contact.
|
||||
* <p>
|
||||
* Locking: subscription write.
|
||||
*/
|
||||
boolean setSubscriptions(T txn, ContactId c, Collection<Group> subs,
|
||||
long version) throws DbException;
|
||||
|
||||
/**
|
||||
* Records a retention ack from the given contact for the given version,
|
||||
* unless the contact has already acked an equal or higher version.
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.sf.briar.db;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
|
||||
import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE;
|
||||
import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS;
|
||||
@@ -37,26 +36,24 @@ import net.sf.briar.api.db.AckAndRequest;
|
||||
import net.sf.briar.api.db.ContactExistsException;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.LocalAuthorExistsException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchContactException;
|
||||
import net.sf.briar.api.db.NoSuchLocalAuthorException;
|
||||
import net.sf.briar.api.db.NoSuchMessageException;
|
||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||
import net.sf.briar.api.db.NoSuchTransportException;
|
||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
||||
import net.sf.briar.api.db.event.ContactAddedEvent;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.LocalAuthorAddedEvent;
|
||||
import net.sf.briar.api.db.event.LocalAuthorRemovedEvent;
|
||||
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.MessageReceivedEvent;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
|
||||
@@ -274,6 +271,31 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGroup(Group g) throws DbException {
|
||||
boolean added = false;
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsGroup(txn, g.getId()))
|
||||
added = db.addGroup(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
if(added) callListeners(new SubscriptionAddedEvent(g));
|
||||
return added;
|
||||
}
|
||||
|
||||
public void addLocalAuthor(LocalAuthor a) throws DbException {
|
||||
contactLock.writeLock().lock();
|
||||
try {
|
||||
@@ -323,8 +345,8 @@ DatabaseCleaner.Callback {
|
||||
callListeners(new LocalAuthorAddedEvent(a.getId()));
|
||||
}
|
||||
|
||||
public void addLocalGroupMessage(Message m) throws DbException {
|
||||
boolean added = false;
|
||||
public void addLocalMessage(Message m) throws DbException {
|
||||
boolean duplicate;
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.writeLock().lock();
|
||||
@@ -333,11 +355,12 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
// Don't store the message if the user has
|
||||
// unsubscribed from the group
|
||||
GroupId g = m.getGroup().getId();
|
||||
if(db.containsSubscription(txn, g))
|
||||
added = storeGroupMessage(txn, m, null);
|
||||
duplicate = db.containsMessage(txn, m.getId());
|
||||
if(!duplicate) {
|
||||
GroupId g = m.getGroup().getId();
|
||||
if(db.containsGroup(txn, g))
|
||||
addMessage(txn, m, null);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -352,91 +375,30 @@ DatabaseCleaner.Callback {
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
if(added)
|
||||
callListeners(new GroupMessageAddedEvent(m.getGroup(), false));
|
||||
if(!duplicate)
|
||||
callListeners(new MessageAddedEvent(m.getGroup(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given message is already in the database, marks it as seen by the
|
||||
* sender and returns false. Otherwise stores the message, marks it as seen
|
||||
* by the sender and unseen by all other contacts, and returns true.
|
||||
* Stores the given message, marks it as read if it was locally generated,
|
||||
* otherwise marks it as seen by the sender, and marks it as unseen by all
|
||||
* other contacts.
|
||||
* <p>
|
||||
* Locking: contact read, message write.
|
||||
* @param sender is null for a locally generated message.
|
||||
* Locking: contact read, message write, subscription read.
|
||||
* @param sender null for a locally generated message.
|
||||
*/
|
||||
private boolean storeGroupMessage(T txn, Message m, ContactId sender)
|
||||
private void addMessage(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);
|
||||
// Mark the message as seen by the sender
|
||||
db.addMessage(txn, m, sender != null);
|
||||
MessageId id = m.getId();
|
||||
if(sender != null) db.addStatus(txn, sender, id, true);
|
||||
if(stored) {
|
||||
// Mark the message as unseen by other contacts
|
||||
for(ContactId c : db.getContactIds(txn))
|
||||
if(!c.equals(sender)) db.addStatus(txn, c, id, false);
|
||||
// Count the bytes stored
|
||||
synchronized(spaceLock) {
|
||||
bytesStoredSinceLastCheck += m.getSerialised().length;
|
||||
}
|
||||
} else {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Duplicate group message not stored");
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
public void addLocalPrivateMessage(Message m, ContactId c)
|
||||
throws DbException {
|
||||
boolean added;
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
added = storePrivateMessage(txn, m, c, false);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
if(added) callListeners(new PrivateMessageAddedEvent(c, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given message is already in the database, returns false.
|
||||
* Otherwise stores the message and marks it as seen or unseen with respect
|
||||
* to the given contact, depending on whether the message is incoming or
|
||||
* outgoing, respectively.
|
||||
* <p>
|
||||
* Locking: message write.
|
||||
*/
|
||||
private boolean storePrivateMessage(T txn, Message m, ContactId c,
|
||||
boolean incoming) throws DbException {
|
||||
if(m.getGroup() != null) throw new IllegalArgumentException();
|
||||
if(m.getAuthor() != null) throw new IllegalArgumentException();
|
||||
if(!db.addPrivateMessage(txn, m, c, incoming)) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Duplicate private message not stored");
|
||||
return false;
|
||||
}
|
||||
if(!incoming) db.setReadFlag(txn, m.getId(), true);
|
||||
db.addStatus(txn, c, m.getId(), incoming);
|
||||
if(sender == null) db.setReadFlag(txn, id, true);
|
||||
else db.addStatus(txn, sender, id, true);
|
||||
for(ContactId c : db.getContactIds(txn))
|
||||
if(!c.equals(sender)) db.addStatus(txn, c, id, false);
|
||||
// Count the bytes stored
|
||||
synchronized(spaceLock) {
|
||||
bytesStoredSinceLastCheck += m.getSerialised().length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addSecrets(Collection<TemporarySecret> secrets)
|
||||
@@ -500,6 +462,35 @@ DatabaseCleaner.Callback {
|
||||
return added;
|
||||
}
|
||||
|
||||
public boolean containsSendableMessages(ContactId c) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
boolean has = db.containsSendableMessages(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return has;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Ack generateAck(ContactId c, int maxMessages) throws DbException {
|
||||
Collection<MessageId> acked;
|
||||
contactLock.readLock().lock();
|
||||
@@ -924,7 +915,7 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsSubscription(txn, g))
|
||||
if(!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
Group group = db.getGroup(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
@@ -938,20 +929,35 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<GroupMessageHeader> getGroupMessageHeaders(GroupId g)
|
||||
throws DbException {
|
||||
messageLock.readLock().lock();
|
||||
public Collection<Group> getGroups() throws DbException {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
Collection<Group> groups = db.getGroups(txn);
|
||||
db.commitTransaction(txn);
|
||||
return groups;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public GroupId getInboxGroup(ContactId c) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsSubscription(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
Collection<GroupMessageHeader> headers =
|
||||
db.getGroupMessageHeaders(txn, g);
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
GroupId inbox = db.getInboxGroup(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return headers;
|
||||
return inbox;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
@@ -960,7 +966,43 @@ DatabaseCleaner.Callback {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<MessageHeader> getInboxMessageHeaders(ContactId c)
|
||||
throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
identityLock.readLock().lock();
|
||||
try {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return headers;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
identityLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1075,32 +1117,29 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
|
||||
ContactId c) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
public Collection<MessageHeader> getMessageHeaders(GroupId g)
|
||||
throws DbException {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
identityLock.readLock().lock();
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
messageLock.readLock().lock();
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
db.getPrivateMessageHeaders(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return headers;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
if(!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
Collection<MessageHeader> headers =
|
||||
db.getMessageHeaders(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
return headers;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
identityLock.readLock().unlock();
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1159,23 +1198,6 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Group> getSubscriptions() throws DbException {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
Collection<Group> subs = db.getSubscriptions(txn);
|
||||
db.commitTransaction(txn);
|
||||
return subs;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<TransportId, Long> getTransportLatencies() throws DbException {
|
||||
transportLock.readLock().lock();
|
||||
try {
|
||||
@@ -1216,7 +1238,7 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsSubscription(txn, g))
|
||||
if(!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
Collection<ContactId> visible = db.getVisibility(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
@@ -1230,61 +1252,6 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<GroupId> getVisibleSubscriptions(ContactId c)
|
||||
throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<GroupId> visible =
|
||||
db.getVisibleSubscriptions(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return visible;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSendableMessages(ContactId c) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
boolean has = db.hasSendableMessages(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return has;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public long incrementConnectionCounter(ContactId c, TransportId t,
|
||||
long period) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
@@ -1374,7 +1341,8 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.removeOutstandingMessages(txn, c, a.getMessageIds());
|
||||
for(MessageId m : a.getMessageIds())
|
||||
db.setStatusSeenIfVisible(txn, c, m);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -1389,7 +1357,7 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
|
||||
public void receiveMessage(ContactId c, Message m) throws DbException {
|
||||
boolean added = false;
|
||||
boolean duplicate, visible;
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.writeLock().lock();
|
||||
@@ -1400,8 +1368,11 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
added = storeMessage(txn, c, m);
|
||||
db.addMessageToAck(txn, c, m.getId());
|
||||
duplicate = db.containsMessage(txn, m.getId());
|
||||
GroupId g = m.getGroup().getId();
|
||||
visible = db.containsVisibleGroup(txn, c, g);
|
||||
if(!duplicate && visible) addMessage(txn, m, c);
|
||||
if(visible) db.addMessageToAck(txn, c, m.getId());
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -1416,36 +1387,8 @@ DatabaseCleaner.Callback {
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
callListeners(new MessageReceivedEvent(c));
|
||||
if(added) {
|
||||
Group g = m.getGroup();
|
||||
if(g == null) callListeners(new PrivateMessageAddedEvent(c, true));
|
||||
else callListeners(new GroupMessageAddedEvent(g, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to store a message received from the given contact, and returns
|
||||
* true if it was stored.
|
||||
* <p>
|
||||
* Locking: contact read, message write, 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);
|
||||
if(visible) callListeners(new MessageReceivedEvent(c));
|
||||
if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), c));
|
||||
}
|
||||
|
||||
public AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException {
|
||||
@@ -1573,7 +1516,7 @@ DatabaseCleaner.Callback {
|
||||
throw new NoSuchContactException();
|
||||
Collection<Group> groups = u.getGroups();
|
||||
long version = u.getVersion();
|
||||
updated = db.setSubscriptions(txn, c, groups, version);
|
||||
updated = db.setGroups(txn, c, groups, version);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -1689,6 +1632,34 @@ DatabaseCleaner.Callback {
|
||||
callListeners(new ContactRemovedEvent(c));
|
||||
}
|
||||
|
||||
public void removeGroup(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.containsGroup(txn, id))
|
||||
throw new NoSuchSubscriptionException();
|
||||
affected = db.getVisibility(txn, id);
|
||||
db.removeGroup(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 removeLocalAuthor(AuthorId a) throws DbException {
|
||||
Collection<ContactId> affected;
|
||||
contactLock.writeLock().lock();
|
||||
@@ -1798,6 +1769,35 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public void setInboxGroup(ContactId c, Group g) throws DbException {
|
||||
if(!g.isPrivate()) throw new IllegalArgumentException();
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.setInboxGroup(txn, c, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
contactLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setReadFlag(MessageId m, boolean read) throws DbException {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
@@ -1880,7 +1880,7 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsSubscription(txn, g))
|
||||
if(!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
// Use HashSets for O(1) lookups, O(n) overall running time
|
||||
HashSet<ContactId> now = new HashSet<ContactId>(visible);
|
||||
@@ -1923,7 +1923,7 @@ DatabaseCleaner.Callback {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!db.containsSubscription(txn, g))
|
||||
if(!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
// Make the group visible or invisible to future contacts
|
||||
db.setVisibleToAll(txn, g, all);
|
||||
@@ -1953,59 +1953,6 @@ DatabaseCleaner.Callback {
|
||||
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;
|
||||
identityLock.writeLock().lock();
|
||||
try {
|
||||
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();
|
||||
}
|
||||
} finally {
|
||||
identityLock.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");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.messaging.GroupFactory;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -43,15 +44,16 @@ class AliceConnector extends Connector {
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory,
|
||||
ConnectionReaderFactory connectionReaderFactory,
|
||||
ConnectionWriterFactory connectionWriterFactory,
|
||||
AuthorFactory authorFactory, KeyManager keyManager,
|
||||
ConnectionDispatcher connectionDispatcher, Clock clock,
|
||||
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
|
||||
AuthorFactory authorFactory, GroupFactory groupFactory,
|
||||
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
|
||||
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor,
|
||||
Map<TransportId, TransportProperties> localProps,
|
||||
PseudoRandom random) {
|
||||
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
|
||||
connectionWriterFactory, authorFactory, keyManager,
|
||||
connectionDispatcher, clock, group, plugin, localAuthor,
|
||||
localProps, random);
|
||||
connectionWriterFactory, authorFactory, groupFactory,
|
||||
keyManager, connectionDispatcher, clock, group, plugin,
|
||||
localAuthor, localProps, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.messaging.GroupFactory;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -43,15 +44,16 @@ class BobConnector extends Connector {
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory,
|
||||
ConnectionReaderFactory connectionReaderFactory,
|
||||
ConnectionWriterFactory connectionWriterFactory,
|
||||
AuthorFactory authorFactory, KeyManager keyManager,
|
||||
ConnectionDispatcher connectionDispatcher, Clock clock,
|
||||
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
|
||||
AuthorFactory authorFactory, GroupFactory groupFactory,
|
||||
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
|
||||
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor,
|
||||
Map<TransportId, TransportProperties> localProps,
|
||||
PseudoRandom random) {
|
||||
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
|
||||
connectionWriterFactory, authorFactory, keyManager,
|
||||
connectionDispatcher, clock, group, plugin, localAuthor,
|
||||
localProps, random);
|
||||
connectionWriterFactory, authorFactory, groupFactory,
|
||||
keyManager, connectionDispatcher, clock, group, plugin,
|
||||
localAuthor, localProps, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,6 +41,8 @@ import net.sf.briar.api.crypto.Signature;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NoSuchTransportException;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
import net.sf.briar.api.messaging.GroupFactory;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -64,6 +66,7 @@ abstract class Connector extends Thread {
|
||||
protected final ConnectionReaderFactory connectionReaderFactory;
|
||||
protected final ConnectionWriterFactory connectionWriterFactory;
|
||||
protected final AuthorFactory authorFactory;
|
||||
protected final GroupFactory groupFactory;
|
||||
protected final KeyManager keyManager;
|
||||
protected final ConnectionDispatcher connectionDispatcher;
|
||||
protected final Clock clock;
|
||||
@@ -84,9 +87,10 @@ abstract class Connector extends Thread {
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory,
|
||||
ConnectionReaderFactory connectionReaderFactory,
|
||||
ConnectionWriterFactory connectionWriterFactory,
|
||||
AuthorFactory authorFactory, KeyManager keyManager,
|
||||
ConnectionDispatcher connectionDispatcher, Clock clock,
|
||||
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
|
||||
AuthorFactory authorFactory, GroupFactory groupFactory,
|
||||
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
|
||||
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor,
|
||||
Map<TransportId, TransportProperties> localProps,
|
||||
PseudoRandom random) {
|
||||
super("Connector");
|
||||
@@ -97,6 +101,7 @@ abstract class Connector extends Thread {
|
||||
this.connectionReaderFactory = connectionReaderFactory;
|
||||
this.connectionWriterFactory = connectionWriterFactory;
|
||||
this.authorFactory = authorFactory;
|
||||
this.groupFactory = groupFactory;
|
||||
this.keyManager = keyManager;
|
||||
this.connectionDispatcher = connectionDispatcher;
|
||||
this.clock = clock;
|
||||
@@ -267,6 +272,11 @@ abstract class Connector extends Thread {
|
||||
long epoch, boolean alice) throws DbException {
|
||||
// Add the contact to the database
|
||||
contactId = db.addContact(remoteAuthor, localAuthor.getId());
|
||||
// Create and store the inbox group
|
||||
byte[] salt = crypto.deriveGroupSalt(secret);
|
||||
Group inbox = groupFactory.createGroup("Inbox", salt, true);
|
||||
db.addGroup(inbox);
|
||||
db.setInboxGroup(contactId, inbox);
|
||||
// Store the remote transport properties
|
||||
db.setRemoteProperties(contactId, remoteProps);
|
||||
// Create an endpoint for each transport shared with the contact
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.invitation.InvitationListener;
|
||||
import net.sf.briar.api.invitation.InvitationState;
|
||||
import net.sf.briar.api.invitation.InvitationTask;
|
||||
import net.sf.briar.api.messaging.GroupFactory;
|
||||
import net.sf.briar.api.plugins.PluginManager;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
@@ -48,6 +49,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
|
||||
private final ConnectionReaderFactory connectionReaderFactory;
|
||||
private final ConnectionWriterFactory connectionWriterFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final GroupFactory groupFactory;
|
||||
private final KeyManager keyManager;
|
||||
private final ConnectionDispatcher connectionDispatcher;
|
||||
private final Clock clock;
|
||||
@@ -74,9 +76,9 @@ class ConnectorGroup extends Thread implements InvitationTask {
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory,
|
||||
ConnectionReaderFactory connectionReaderFactory,
|
||||
ConnectionWriterFactory connectionWriterFactory,
|
||||
AuthorFactory authorFactory, KeyManager keyManager,
|
||||
ConnectionDispatcher connectionDispatcher, Clock clock,
|
||||
PluginManager pluginManager, AuthorId localAuthorId,
|
||||
AuthorFactory authorFactory, GroupFactory groupFactory,
|
||||
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
|
||||
Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
|
||||
int localInvitationCode, int remoteInvitationCode) {
|
||||
super("ConnectorGroup");
|
||||
this.crypto = crypto;
|
||||
@@ -86,6 +88,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
|
||||
this.connectionReaderFactory = connectionReaderFactory;
|
||||
this.connectionWriterFactory = connectionWriterFactory;
|
||||
this.authorFactory = authorFactory;
|
||||
this.groupFactory = groupFactory;
|
||||
this.keyManager = keyManager;
|
||||
this.connectionDispatcher = connectionDispatcher;
|
||||
this.clock = clock;
|
||||
@@ -171,8 +174,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
|
||||
remoteInvitationCode);
|
||||
return new AliceConnector(crypto, db, readerFactory, writerFactory,
|
||||
connectionReaderFactory, connectionWriterFactory, authorFactory,
|
||||
keyManager, connectionDispatcher, clock, this, plugin,
|
||||
localAuthor, localProps, random);
|
||||
groupFactory, keyManager, connectionDispatcher, clock, this,
|
||||
plugin, localAuthor, localProps, random);
|
||||
}
|
||||
|
||||
private Connector createBobConnector(DuplexPlugin plugin,
|
||||
@@ -182,8 +185,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
|
||||
localInvitationCode);
|
||||
return new BobConnector(crypto, db, readerFactory, writerFactory,
|
||||
connectionReaderFactory, connectionWriterFactory, authorFactory,
|
||||
keyManager, connectionDispatcher, clock, this, plugin,
|
||||
localAuthor, localProps, random);
|
||||
groupFactory, keyManager, connectionDispatcher, clock, this,
|
||||
plugin, localAuthor, localProps, random);
|
||||
}
|
||||
|
||||
public void localConfirmationSucceeded() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.invitation.InvitationTask;
|
||||
import net.sf.briar.api.invitation.InvitationTaskFactory;
|
||||
import net.sf.briar.api.messaging.GroupFactory;
|
||||
import net.sf.briar.api.plugins.PluginManager;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
@@ -26,6 +27,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
private final ConnectionReaderFactory connectionReaderFactory;
|
||||
private final ConnectionWriterFactory connectionWriterFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final GroupFactory groupFactory;
|
||||
private final KeyManager keyManager;
|
||||
private final ConnectionDispatcher connectionDispatcher;
|
||||
private final Clock clock;
|
||||
@@ -36,9 +38,9 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory,
|
||||
ConnectionReaderFactory connectionReaderFactory,
|
||||
ConnectionWriterFactory connectionWriterFactory,
|
||||
AuthorFactory authorFactory, KeyManager keyManager,
|
||||
ConnectionDispatcher connectionDispatcher, Clock clock,
|
||||
PluginManager pluginManager) {
|
||||
AuthorFactory authorFactory, GroupFactory groupFactory,
|
||||
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
|
||||
Clock clock, PluginManager pluginManager) {
|
||||
this.crypto = crypto;
|
||||
this.db = db;
|
||||
this.readerFactory = readerFactory;
|
||||
@@ -46,6 +48,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
this.connectionReaderFactory = connectionReaderFactory;
|
||||
this.connectionWriterFactory = connectionWriterFactory;
|
||||
this.authorFactory = authorFactory;
|
||||
this.groupFactory = groupFactory;
|
||||
this.keyManager = keyManager;
|
||||
this.connectionDispatcher = connectionDispatcher;
|
||||
this.clock = clock;
|
||||
@@ -56,7 +59,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
int remoteCode) {
|
||||
return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
|
||||
connectionReaderFactory, connectionWriterFactory,
|
||||
authorFactory, keyManager, connectionDispatcher, clock,
|
||||
pluginManager, localAuthorId, localCode, remoteCode);
|
||||
authorFactory, groupFactory, keyManager, connectionDispatcher,
|
||||
clock, pluginManager, localAuthorId, localCode, remoteCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,24 +27,28 @@ class AuthorFactoryImpl implements AuthorFactory {
|
||||
this.writerFactory = writerFactory;
|
||||
}
|
||||
|
||||
public Author createAuthor(String name, byte[] publicKey)
|
||||
throws IOException {
|
||||
public Author createAuthor(String name, byte[] publicKey) {
|
||||
return new Author(getId(name, publicKey), name, publicKey);
|
||||
}
|
||||
|
||||
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
|
||||
byte[] privateKey) throws IOException {
|
||||
byte[] privateKey) {
|
||||
return new LocalAuthor(getId(name, publicKey), name, publicKey,
|
||||
privateKey);
|
||||
}
|
||||
|
||||
private AuthorId getId(String name, byte[] publicKey) throws IOException {
|
||||
private AuthorId getId(String name, byte[] publicKey) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(AUTHOR);
|
||||
w.writeString(name);
|
||||
w.writeBytes(publicKey);
|
||||
w.writeStructEnd();
|
||||
try {
|
||||
w.writeStructStart(AUTHOR);
|
||||
w.writeString(name);
|
||||
w.writeBytes(publicKey);
|
||||
w.writeStructEnd();
|
||||
} catch(IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException();
|
||||
}
|
||||
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||
messageDigest.update(out.toByteArray());
|
||||
return new AuthorId(messageDigest.digest());
|
||||
|
||||
@@ -27,22 +27,28 @@ class GroupFactoryImpl implements GroupFactory {
|
||||
this.writerFactory = writerFactory;
|
||||
}
|
||||
|
||||
public Group createGroup(String name) throws IOException {
|
||||
public Group createGroup(String name, boolean isPrivate) {
|
||||
byte[] salt = new byte[GROUP_SALT_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(salt);
|
||||
return createGroup(name, salt);
|
||||
return createGroup(name, salt, isPrivate);
|
||||
}
|
||||
|
||||
public Group createGroup(String name, byte[] salt) throws IOException {
|
||||
public Group createGroup(String name, byte[] salt, boolean isPrivate) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(GROUP);
|
||||
w.writeString(name);
|
||||
w.writeBytes(salt);
|
||||
w.writeStructEnd();
|
||||
try {
|
||||
w.writeStructStart(GROUP);
|
||||
w.writeString(name);
|
||||
w.writeBytes(salt);
|
||||
w.writeBoolean(isPrivate);
|
||||
w.writeStructEnd();
|
||||
} catch(IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException();
|
||||
}
|
||||
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||
messageDigest.update(out.toByteArray());
|
||||
GroupId id = new GroupId(messageDigest.digest());
|
||||
return new Group(id, name, salt);
|
||||
return new Group(id, name, salt, isPrivate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ class GroupReader implements StructReader<Group> {
|
||||
byte[] publicKey = null;
|
||||
if(r.hasNull()) r.readNull();
|
||||
else publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
boolean isPrivate = r.readBoolean();
|
||||
r.readStructEnd();
|
||||
r.removeConsumer(digesting);
|
||||
// Build and return the group
|
||||
GroupId id = new GroupId(messageDigest.digest());
|
||||
return new Group(id, name, publicKey);
|
||||
return new Group(id, name, publicKey, isPrivate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,13 +47,6 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
this.writerFactory = writerFactory;
|
||||
}
|
||||
|
||||
public Message createPrivateMessage(MessageId parent, String contentType,
|
||||
long timestamp, byte[] body) throws IOException,
|
||||
GeneralSecurityException {
|
||||
return createMessage(parent, null, null, null, contentType, timestamp,
|
||||
body);
|
||||
}
|
||||
|
||||
public Message createAnonymousMessage(MessageId parent, Group group,
|
||||
String contentType, long timestamp, byte[] body) throws IOException,
|
||||
GeneralSecurityException {
|
||||
@@ -97,8 +90,7 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
w.writeStructStart(MESSAGE);
|
||||
if(parent == null) w.writeNull();
|
||||
else w.writeBytes(parent.getBytes());
|
||||
if(group == null) w.writeNull();
|
||||
else writeGroup(w, group);
|
||||
writeGroup(w, group);
|
||||
if(author == null) w.writeNull();
|
||||
else writeAuthor(w, author);
|
||||
w.writeString(contentType);
|
||||
@@ -130,6 +122,7 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
w.writeStructStart(GROUP);
|
||||
w.writeString(g.getName());
|
||||
w.writeBytes(g.getSalt());
|
||||
w.writeBoolean(g.isPrivate());
|
||||
w.writeStructEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,10 @@ import static net.sf.briar.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
|
||||
import static net.sf.briar.api.messaging.Types.MESSAGE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.FormatException;
|
||||
@@ -28,13 +24,11 @@ class MessageReader implements StructReader<UnverifiedMessage> {
|
||||
|
||||
private final StructReader<Group> groupReader;
|
||||
private final StructReader<Author> authorReader;
|
||||
private final CharsetDecoder decoder;
|
||||
|
||||
MessageReader(StructReader<Group> groupReader,
|
||||
StructReader<Author> authorReader) {
|
||||
this.groupReader = groupReader;
|
||||
this.authorReader = authorReader;
|
||||
decoder = Charset.forName("UTF-8").newDecoder();
|
||||
}
|
||||
|
||||
public UnverifiedMessage readStruct(Reader r) throws IOException {
|
||||
@@ -53,10 +47,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
|
||||
if(b.length < UniqueId.LENGTH) throw new FormatException();
|
||||
parent = new MessageId(b);
|
||||
}
|
||||
// Read the group, if there is one
|
||||
Group group = null;
|
||||
if(r.hasNull()) r.readNull();
|
||||
else group = groupReader.readStruct(r);
|
||||
// Read the group
|
||||
Group group = groupReader.readStruct(r);
|
||||
// Read the author, if there is one
|
||||
Author author = null;
|
||||
if(r.hasNull()) r.readNull();
|
||||
@@ -71,16 +63,6 @@ class MessageReader implements StructReader<UnverifiedMessage> {
|
||||
if(salt.length < MESSAGE_SALT_LENGTH) throw new FormatException();
|
||||
// Read the message body
|
||||
byte[] body = r.readBytes(MAX_BODY_LENGTH);
|
||||
// If the content type is text/plain, extract a subject line
|
||||
String subject;
|
||||
if(contentType.equals("text/plain")) {
|
||||
byte[] start = new byte[Math.min(MAX_SUBJECT_LENGTH, body.length)];
|
||||
System.arraycopy(body, 0, start, 0, start.length);
|
||||
decoder.reset();
|
||||
subject = decoder.decode(ByteBuffer.wrap(start)).toString();
|
||||
} else {
|
||||
subject = "";
|
||||
}
|
||||
// Record the offset of the body within the message
|
||||
int bodyStart = (int) counting.getCount() - body.length;
|
||||
// Record the length of the data covered by the author's signature
|
||||
@@ -96,7 +78,7 @@ class MessageReader implements StructReader<UnverifiedMessage> {
|
||||
r.removeConsumer(copying);
|
||||
byte[] raw = copying.getCopy();
|
||||
return new UnverifiedMessage(parent, group, author, contentType,
|
||||
subject, timestamp, raw, signature, bodyStart, body.length,
|
||||
timestamp, raw, signature, bodyStart, body.length,
|
||||
signedLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package net.sf.briar.messaging;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import net.sf.briar.api.Author;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
@@ -18,11 +21,13 @@ import net.sf.briar.api.messaging.UnverifiedMessage;
|
||||
class MessageVerifierImpl implements MessageVerifier {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
private final KeyParser keyParser;
|
||||
|
||||
@Inject
|
||||
MessageVerifierImpl(CryptoComponent crypto) {
|
||||
MessageVerifierImpl(CryptoComponent crypto, Clock clock) {
|
||||
this.crypto = crypto;
|
||||
this.clock = clock;
|
||||
keyParser = crypto.getSignatureKeyParser();
|
||||
}
|
||||
|
||||
@@ -30,7 +35,11 @@ class MessageVerifierImpl implements MessageVerifier {
|
||||
throws GeneralSecurityException {
|
||||
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||
Signature signature = crypto.getSignature();
|
||||
// Hash the message, including the signature, to get the message ID
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if(m.getTimestamp() > now + MAX_CLOCK_DIFFERENCE)
|
||||
throw new GeneralSecurityException();
|
||||
// Hash the message to get the message ID
|
||||
byte[] raw = m.getSerialised();
|
||||
messageDigest.update(raw);
|
||||
MessageId id = new MessageId(messageDigest.digest());
|
||||
|
||||
@@ -129,6 +129,7 @@ class PacketWriterImpl implements PacketWriter {
|
||||
w.writeStructStart(GROUP);
|
||||
w.writeString(g.getName());
|
||||
w.writeBytes(g.getSalt());
|
||||
w.writeBoolean(g.isPrivate());
|
||||
w.writeStructEnd();
|
||||
}
|
||||
w.writeListEnd();
|
||||
|
||||
@@ -32,10 +32,10 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
|
||||
// Read the start of the struct
|
||||
r.readStructStart(SUBSCRIPTION_UPDATE);
|
||||
// Read the subscriptions
|
||||
List<Group> subs = new ArrayList<Group>();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
r.readListStart();
|
||||
for(int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++)
|
||||
subs.add(groupReader.readStruct(r));
|
||||
groups.add(groupReader.readStruct(r));
|
||||
r.readListEnd();
|
||||
// Read the version number
|
||||
long version = r.readIntAny();
|
||||
@@ -45,7 +45,7 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
|
||||
// Reset the reader
|
||||
r.removeConsumer(counting);
|
||||
// Build and return the subscription update
|
||||
subs = Collections.unmodifiableList(subs);
|
||||
return new SubscriptionUpdate(subs, version);
|
||||
groups = Collections.unmodifiableList(groups);
|
||||
return new SubscriptionUpdate(groups, version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,11 @@ import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||
import net.sf.briar.api.db.event.MessageReceivedEvent;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
|
||||
@@ -129,7 +128,7 @@ abstract class DuplexConnection implements DatabaseListener {
|
||||
if(e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if(contactId.equals(c.getContactId())) writerTasks.add(CLOSE);
|
||||
} else if(e instanceof GroupMessageAddedEvent) {
|
||||
} else if(e instanceof MessageAddedEvent) {
|
||||
if(canSendOffer.getAndSet(false))
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
@@ -147,12 +146,6 @@ abstract class DuplexConnection implements DatabaseListener {
|
||||
} else if(e instanceof MessageReceivedEvent) {
|
||||
if(((MessageReceivedEvent) e).getContactId().equals(contactId))
|
||||
dbExecutor.execute(new GenerateAcks());
|
||||
} else if(e instanceof PrivateMessageAddedEvent) {
|
||||
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
|
||||
if(!p.isIncoming() && p.getContactId().equals(contactId)) {
|
||||
if(canSendOffer.getAndSet(false))
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
} else if(e instanceof RemoteRetentionTimeUpdatedEvent) {
|
||||
dbExecutor.execute(new GenerateRetentionAck());
|
||||
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
new Random().nextBytes(secret);
|
||||
// Create a group
|
||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||
group = groupFactory.createGroup("Group");
|
||||
group = groupFactory.createGroup("Group", false);
|
||||
// Create an author
|
||||
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||
|
||||
@@ -31,11 +31,11 @@ import net.sf.briar.api.db.NoSuchTransportException;
|
||||
import net.sf.briar.api.db.event.ContactAddedEvent;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.LocalAuthorAddedEvent;
|
||||
import net.sf.briar.api.db.event.LocalAuthorRemovedEvent;
|
||||
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageReceivedEvent;
|
||||
import net.sf.briar.api.db.event.SubscriptionAddedEvent;
|
||||
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
|
||||
import net.sf.briar.api.lifecycle.ShutdownManager;
|
||||
@@ -68,12 +68,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
protected final Author author;
|
||||
protected final AuthorId localAuthorId;
|
||||
protected final LocalAuthor localAuthor;
|
||||
protected final MessageId messageId, messageId1, privateMessageId;
|
||||
protected final MessageId messageId, messageId1;
|
||||
protected final String contentType, subject;
|
||||
protected final long timestamp;
|
||||
protected final int size;
|
||||
protected final byte[] raw;
|
||||
protected final Message message, privateMessage;
|
||||
protected final Message message, message1;
|
||||
protected final TransportId transportId;
|
||||
protected final TransportProperties transportProperties;
|
||||
protected final ContactId contactId;
|
||||
@@ -83,7 +83,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
|
||||
public DatabaseComponentTest() {
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], true);
|
||||
authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||
@@ -91,7 +91,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
privateMessageId = new MessageId(TestUtils.getRandomId());
|
||||
contentType = "text/plain";
|
||||
subject = "Foo";
|
||||
timestamp = System.currentTimeMillis();
|
||||
@@ -99,7 +98,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(privateMessageId, null, null, null,
|
||||
message1 = new TestMessage(messageId1, messageId, group, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
transportProperties = new TransportProperties(Collections.singletonMap(
|
||||
@@ -136,13 +135,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
with(any(long.class)));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
// addLocalAuthor(localAuthor)
|
||||
// addLocalAuthor()
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addLocalAuthor(txn, localAuthor);
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
LocalAuthorAddedEvent.class)));
|
||||
// addContact(author, localAuthorId)
|
||||
// addContact()
|
||||
oneOf(database).containsContact(txn, authorId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||
@@ -153,43 +152,43 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
// getContacts()
|
||||
oneOf(database).getContacts(txn);
|
||||
will(returnValue(Arrays.asList(contact)));
|
||||
// getRemoteProperties(transportId)
|
||||
// getRemoteProperties()
|
||||
oneOf(database).getRemoteProperties(txn, transportId);
|
||||
will(returnValue(Collections.emptyMap()));
|
||||
// subscribe(group)
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
// addGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addSubscription(txn, group);
|
||||
oneOf(database).addGroup(txn, group);
|
||||
will(returnValue(true));
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
SubscriptionAddedEvent.class)));
|
||||
// subscribe(group) again
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
// addGroup() again
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
// getMessageHeaders(groupId)
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
// getMessageHeaders()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupMessageHeaders(txn, groupId);
|
||||
oneOf(database).getMessageHeaders(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
// getSubscriptions()
|
||||
oneOf(database).getSubscriptions(txn);
|
||||
// getGroups()
|
||||
oneOf(database).getGroups(txn);
|
||||
will(returnValue(Arrays.asList(groupId)));
|
||||
// unsubscribe(groupId)
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
// removeGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(database).removeSubscription(txn, groupId);
|
||||
oneOf(database).removeGroup(txn, groupId);
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
SubscriptionRemovedEvent.class)));
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
LocalSubscriptionsUpdatedEvent.class)));
|
||||
// removeContact(contactId)
|
||||
// removeContact()
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).removeContact(txn, contactId);
|
||||
oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class)));
|
||||
// removeLocalAuthor(localAuthorId)
|
||||
// removeLocalAuthor()
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getContacts(txn, localAuthorId);
|
||||
@@ -212,12 +211,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
assertEquals(Arrays.asList(contact), db.getContacts());
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getRemoteProperties(transportId));
|
||||
db.subscribe(group); // First time - listeners called
|
||||
db.subscribe(group); // Second time - not called
|
||||
assertEquals(Collections.emptyList(),
|
||||
db.getGroupMessageHeaders(groupId));
|
||||
assertEquals(Arrays.asList(groupId), db.getSubscriptions());
|
||||
db.unsubscribe(group);
|
||||
db.addGroup(group); // First time - listeners called
|
||||
db.addGroup(group); // Second time - not called
|
||||
assertEquals(Collections.emptyList(), db.getMessageHeaders(groupId));
|
||||
assertEquals(Arrays.asList(groupId), db.getGroups());
|
||||
db.removeGroup(group);
|
||||
db.removeContact(contactId);
|
||||
db.removeLocalAuthor(localAuthorId);
|
||||
db.removeListener(listener);
|
||||
@@ -227,7 +225,29 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupMessagesAreNotStoredUnlessSubscribed()
|
||||
public void testDuplicateLocalMessagesAreNotStored() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalMessagesAreNotStoredUnlessSubscribed()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -235,125 +255,52 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalGroupMessage(message)
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalGroupMessage(message);
|
||||
db.addLocalMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateGroupMessagesAreNotStored() throws Exception {
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalGroupMessage(message)
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addGroupMessage(txn, message, false);
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalGroupMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalGroupMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalGroupMessage(message)
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addGroupMessage(txn, message, false);
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addMessage(txn, message, false);
|
||||
oneOf(database).setReadFlag(txn, messageId, true);
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(Arrays.asList(contactId)));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was added, so the listener should be called
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
MessageAddedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalGroupMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatePrivateMessagesAreNotStored() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// addLocalPrivateMessage(privateMessage, contactId)
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
false);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalPrivateMessage(privateMessage, contactId);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalPrivateMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// addLocalPrivateMessage(privateMessage, contactId)
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
false);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setReadFlag(txn, privateMessageId, true);
|
||||
oneOf(database).addStatus(txn, contactId, privateMessageId, false);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addLocalPrivateMessage(privateMessage, contactId);
|
||||
db.addListener(listener);
|
||||
db.addLocalMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
@@ -383,7 +330,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
try {
|
||||
db.addLocalPrivateMessage(privateMessage, contactId);
|
||||
db.containsSendableMessages(contactId);
|
||||
fail();
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
@@ -443,12 +390,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
try {
|
||||
db.getVisibleSubscriptions(contactId);
|
||||
fail();
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
try {
|
||||
db.hasSendableMessages(contactId);
|
||||
db.getInboxGroup(contactId);
|
||||
fail();
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
@@ -522,6 +464,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
fail();
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
try {
|
||||
db.setInboxGroup(contactId, group);
|
||||
fail();
|
||||
} catch(NoSuchContactException expected) {}
|
||||
|
||||
try {
|
||||
db.setSeen(contactId, Arrays.asList(messageId));
|
||||
fail();
|
||||
@@ -582,7 +529,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
// Check whether the subscription is in the DB (which it's not)
|
||||
exactly(5).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(5).of(database).containsSubscription(txn, groupId);
|
||||
exactly(5).of(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
exactly(5).of(database).abortTransaction(txn);
|
||||
}});
|
||||
@@ -595,7 +542,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
} catch(NoSuchSubscriptionException expected) {}
|
||||
|
||||
try {
|
||||
db.getGroupMessageHeaders(groupId);
|
||||
db.getMessageHeaders(groupId);
|
||||
fail();
|
||||
} catch(NoSuchSubscriptionException expected) {}
|
||||
|
||||
@@ -605,12 +552,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
} catch(NoSuchSubscriptionException expected) {}
|
||||
|
||||
try {
|
||||
db.setVisibility(groupId, Collections.<ContactId>emptyList());
|
||||
db.removeGroup(group);
|
||||
fail();
|
||||
} catch(NoSuchSubscriptionException expected) {}
|
||||
|
||||
try {
|
||||
db.unsubscribe(group);
|
||||
db.setVisibility(groupId, Collections.<ContactId>emptyList());
|
||||
fail();
|
||||
} catch(NoSuchSubscriptionException expected) {}
|
||||
|
||||
@@ -626,14 +573,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalAuthor(localAuthor)
|
||||
// addLocalAuthor()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addLocalAuthor(txn, localAuthor);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// addContact(author, localAuthorId)
|
||||
// addContact()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, authorId);
|
||||
@@ -830,12 +777,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// Get the sendable message IDs
|
||||
oneOf(database).getMessagesToOffer(txn, contactId, 123);
|
||||
will(returnValue(messagesToOffer));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -856,11 +803,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE);
|
||||
will(returnValue(null));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -880,11 +827,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE);
|
||||
will(returnValue(new RetentionUpdate(0, 1)));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -907,12 +854,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getSubscriptionUpdate(txn, contactId,
|
||||
Long.MAX_VALUE);
|
||||
will(returnValue(null));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -932,12 +879,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getSubscriptionUpdate(txn, contactId,
|
||||
Long.MAX_VALUE);
|
||||
will(returnValue(new SubscriptionUpdate(Arrays.asList(group), 1)));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -960,11 +907,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE);
|
||||
will(returnValue(null));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -984,12 +931,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE);
|
||||
will(returnValue(Arrays.asList(new TransportUpdate(transportId,
|
||||
transportProperties, 1))));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1016,12 +963,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// Get the acked messages
|
||||
oneOf(database).removeOutstandingMessages(txn, contactId,
|
||||
Arrays.asList(messageId));
|
||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1032,82 +977,92 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceivePrivateMessage() throws Exception {
|
||||
public void testReceiveMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// The message is stored
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
true);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addStatus(txn, contactId, privateMessageId, true);
|
||||
// The message must be acked
|
||||
oneOf(database).addMessageToAck(txn, contactId, privateMessageId);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.receiveMessage(contactId, privateMessage);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDuplicatePrivateMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// The message is not stored, it's a duplicate
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
true);
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
// The message must still be acked
|
||||
oneOf(database).addMessageToAck(txn, contactId, privateMessageId);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.receiveMessage(contactId, privateMessage);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessageDoesNotStoreGroupMessageUnlessSubscribed()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
||||
will(returnValue(true));
|
||||
// Only store messages belonging to visible, subscribed groups
|
||||
oneOf(database).containsVisibleSubscription(txn, contactId,
|
||||
groupId);
|
||||
will(returnValue(false));
|
||||
// The message is not stored but it must still be acked
|
||||
oneOf(database).addMessage(txn, message, true);
|
||||
oneOf(database).addStatus(txn, contactId, messageId, true);
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(Arrays.asList(contactId)));
|
||||
oneOf(database).addMessageToAck(txn, contactId, messageId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was received and added
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
MessageReceivedEvent.class)));
|
||||
oneOf(listener).eventOccurred(with(any(MessageAddedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.receiveMessage(contactId, message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDuplicateMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
||||
will(returnValue(true));
|
||||
// The message wasn't stored but it must still be acked
|
||||
oneOf(database).addMessageToAck(txn, contactId, messageId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was received but not added
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
MessageReceivedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.receiveMessage(contactId, message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1132,7 +1087,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// Get the offered messages
|
||||
@@ -1142,6 +1096,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
will(returnValue(true)); // Visible - ack message # 1
|
||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2);
|
||||
will(returnValue(false)); // Not visible - request message # 2
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1168,10 +1123,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setRetentionUpdateAcked(txn, contactId, 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1192,10 +1147,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setSubscriptionUpdateAcked(txn, contactId, 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1216,11 +1171,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setSubscriptions(txn, contactId,
|
||||
Arrays.asList(group), 1);
|
||||
oneOf(database).setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1241,13 +1195,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsTransport(txn, transportId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setTransportUpdateAcked(txn, contactId,
|
||||
transportId, 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1268,11 +1222,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setRemoteProperties(txn, contactId, transportId,
|
||||
transportProperties, 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1284,132 +1238,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingGroupMessageCallsListeners() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalGroupMessage(message)
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addGroupMessage(txn, message, 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).commitTransaction(txn);
|
||||
// The message was added, so the listener should be called
|
||||
oneOf(listener).eventOccurred(with(any(
|
||||
GroupMessageAddedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.addLocalGroupMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingPrivateMessageCallsListeners() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// addLocalPrivateMessage(privateMessage, contactId)
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
false);
|
||||
will(returnValue(true));
|
||||
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)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.addLocalPrivateMessage(privateMessage, contactId);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingDuplicateGroupMessageDoesNotCallListeners()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
// addLocalGroupMessage(message)
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addGroupMessage(txn, message, false);
|
||||
will(returnValue(false));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was not added, so the listener should not be called
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.addLocalGroupMessage(message);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingDuplicatePrivateMessageDoesNotCallListeners()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final DatabaseListener listener = context.mock(DatabaseListener.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// addLocalPrivateMessage(privateMessage, contactId)
|
||||
oneOf(database).addPrivateMessage(txn, privateMessage, contactId,
|
||||
false);
|
||||
will(returnValue(false));
|
||||
// The message was not added, so the listener should not be called
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
|
||||
db.addListener(listener);
|
||||
db.addLocalPrivateMessage(privateMessage, contactId);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingLocalTransportPropertiesCallsListeners()
|
||||
throws Exception {
|
||||
@@ -1477,11 +1305,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
// setSeen(contactId, Arrays.asList(messageId))
|
||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||
shutdown);
|
||||
@@ -1504,7 +1331,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(both));
|
||||
@@ -1539,7 +1366,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(both));
|
||||
@@ -1558,7 +1385,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingVisibleToAllTrueAffectsCurrentContacts()
|
||||
public void testSettingVisibleToAllAffectsCurrentContacts()
|
||||
throws Exception {
|
||||
final ContactId contactId1 = new ContactId(123);
|
||||
final Collection<ContactId> both = Arrays.asList(contactId, contactId1);
|
||||
@@ -1572,7 +1399,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
// setVisibility()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
@@ -1586,7 +1413,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
||||
// setVisibleToAll()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsSubscription(txn, groupId);
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setVisibleToAll(txn, groupId, true);
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
|
||||
@@ -33,7 +33,7 @@ import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.clock.SystemClock;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.GroupMessageHeader;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.GroupStatus;
|
||||
@@ -59,25 +59,24 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
private final Author author;
|
||||
private final AuthorId localAuthorId;
|
||||
private final LocalAuthor localAuthor;
|
||||
private final MessageId messageId, messageId1;
|
||||
private final MessageId messageId;
|
||||
private final String contentType, subject;
|
||||
private final long timestamp;
|
||||
private final int size;
|
||||
private final byte[] raw;
|
||||
private final Message message, privateMessage;
|
||||
private final Message message;
|
||||
private final TransportId transportId;
|
||||
private final ContactId contactId;
|
||||
|
||||
public H2DatabaseTest() throws Exception {
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], false);
|
||||
authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||
localAuthor = new LocalAuthor(localAuthorId, "Bob",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
contentType = "text/plain";
|
||||
subject = "Foo";
|
||||
timestamp = System.currentTimeMillis();
|
||||
@@ -86,8 +85,6 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
random.nextBytes(raw);
|
||||
message = new TestMessage(messageId, null, group, author, contentType,
|
||||
subject, timestamp, raw);
|
||||
privateMessage = new TestMessage(messageId1, null, null, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
contactId = new ContactId(1);
|
||||
}
|
||||
@@ -106,15 +103,12 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertFalse(db.containsSubscription(txn, groupId));
|
||||
db.addSubscription(txn, group);
|
||||
assertTrue(db.containsSubscription(txn, groupId));
|
||||
assertFalse(db.containsGroup(txn, groupId));
|
||||
db.addGroup(txn, group);
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
db.addGroupMessage(txn, message, true);
|
||||
db.addMessage(txn, message, true);
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
assertFalse(db.containsMessage(txn, messageId1));
|
||||
db.addPrivateMessage(txn, privateMessage, contactId, true);
|
||||
assertTrue(db.containsMessage(txn, messageId1));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
@@ -122,18 +116,14 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db = open(true);
|
||||
txn = db.startTransaction();
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertTrue(db.containsSubscription(txn, groupId));
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
byte[] raw1 = db.getRawMessage(txn, messageId);
|
||||
assertArrayEquals(raw, raw1);
|
||||
assertTrue(db.containsMessage(txn, messageId1));
|
||||
raw1 = db.getRawMessage(txn, messageId1);
|
||||
assertArrayEquals(raw, raw1);
|
||||
// Delete the records
|
||||
db.removeMessage(txn, messageId);
|
||||
db.removeMessage(txn, messageId1);
|
||||
db.removeContact(txn, contactId);
|
||||
db.removeSubscription(txn, groupId);
|
||||
db.removeGroup(txn, groupId);
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
@@ -143,25 +133,24 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getRemoteProperties(txn, transportId));
|
||||
assertFalse(db.containsSubscription(txn, groupId));
|
||||
assertFalse(db.containsGroup(txn, groupId));
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
assertFalse(db.containsMessage(txn, messageId1));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribingRemovesGroupMessage() throws Exception {
|
||||
public void testUnsubscribingRemovesMessage() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group and store a message
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// Unsubscribing from the group should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
db.removeSubscription(txn, groupId);
|
||||
db.removeGroup(txn, groupId);
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -169,105 +158,27 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingContactRemovesPrivateMessage() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and store a private message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addPrivateMessage(txn, privateMessage, contactId, false);
|
||||
|
||||
// Removing the contact should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId1));
|
||||
db.removeContact(txn, contactId);
|
||||
assertFalse(db.containsMessage(txn, messageId1));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendablePrivateMessagesMustHaveSeenFlagFalse()
|
||||
throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and store a private message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addPrivateMessage(txn, privateMessage, contactId, false);
|
||||
|
||||
// The message has no status yet, so it should not be sendable
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// Adding a status with seen = false should make the message sendable
|
||||
db.addStatus(txn, contactId, messageId1, false);
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId1, it.next());
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendablePrivateMessagesMustFitCapacity()
|
||||
throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and store a private message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addPrivateMessage(txn, privateMessage, contactId, false);
|
||||
db.addStatus(txn, contactId, messageId1, false);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, size - 1).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// The message is just the right size to send
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, size).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId1, it.next());
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableGroupMessagesMustHaveSeenFlagFalse()
|
||||
throws Exception {
|
||||
public void testSendableMessagesMustHaveSeenFlagFalse() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// The message has no status yet, so it should not be sendable
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
assertFalse(db.containsSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// Adding a status with seen = false should make the message sendable
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
assertTrue(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId, it.next());
|
||||
@@ -275,7 +186,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// Changing the status to seen = true should make the message unsendable
|
||||
db.setStatusSeenIfVisible(txn, contactId, messageId);
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
assertFalse(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
@@ -284,35 +195,35 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableGroupMessagesMustBeSubscribed() throws Exception {
|
||||
public void testSendableMessagesMustBeSubscribed() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// The contact is not subscribed, so the message should not be sendable
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
assertFalse(db.containsSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// The contact subscribing should make the message sendable
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
assertTrue(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId, it.next());
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// The contact unsubscribing should make the message unsendable
|
||||
db.setSubscriptions(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
assertFalse(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
@@ -321,27 +232,27 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableGroupMessagesMustFitCapacity() throws Exception {
|
||||
public void testSendableMessagesMustFitCapacity() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
assertTrue(db.containsSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, size - 1).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// The message is just the right size to send
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
assertTrue(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, size).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId, it.next());
|
||||
@@ -352,28 +263,28 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableGroupMessagesMustBeVisible() throws Exception {
|
||||
public void testSendableMessagesMustBeVisible() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addGroup(txn, group);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// The subscription is not visible to the contact, so the message
|
||||
// should not be sendable
|
||||
assertFalse(db.hasSendableMessages(txn, contactId));
|
||||
assertFalse(db.containsSendableMessages(txn, contactId));
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// Making the subscription visible should make the message sendable
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
assertTrue(db.hasSendableMessages(txn, contactId));
|
||||
assertTrue(db.containsSendableMessages(txn, contactId));
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(messageId, it.next());
|
||||
@@ -389,6 +300,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and some messages to ack
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addMessageToAck(txn, contactId, messageId);
|
||||
@@ -443,10 +355,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
@@ -463,7 +375,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
// Pretend that the message was acked
|
||||
db.removeOutstandingMessages(txn, contactId, Arrays.asList(messageId));
|
||||
db.setStatusSeenIfVisible(txn, contactId, messageId);
|
||||
|
||||
// The message still should not be sendable
|
||||
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||
@@ -482,9 +394,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group and store two messages
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addGroupMessage(txn, message1, false);
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addMessage(txn, message1, false);
|
||||
|
||||
// Allowing enough capacity for one message should return the older one
|
||||
Iterator<MessageId> it = db.getOldMessages(txn, size).iterator();
|
||||
@@ -507,7 +419,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
public void testGetFreeSpace() throws Exception {
|
||||
byte[] largeBody = new byte[ONE_MEGABYTE];
|
||||
for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
|
||||
Message message1 = new TestMessage(messageId, null, group, author,
|
||||
Message message = new TestMessage(messageId, null, group, author,
|
||||
contentType, subject, timestamp, largeBody);
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
@@ -521,8 +433,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// Storing a message should reduce the free space
|
||||
Connection txn = db.startTransaction();
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroupMessage(txn, message1, false);
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, false);
|
||||
db.commitTransaction(txn);
|
||||
assertTrue(db.getFreeSpace() < free);
|
||||
|
||||
@@ -741,9 +653,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact and subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
|
||||
// The message is not in the database
|
||||
assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
|
||||
@@ -760,10 +672,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// Set the status to seen = true
|
||||
db.addStatus(txn, contactId, messageId, true);
|
||||
@@ -784,11 +696,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// the message is older than the contact's retention time
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setRetentionTime(txn, contactId, timestamp + 1, 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// Set the status to seen = false
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
@@ -808,10 +720,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// Set the status to seen = false
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
@@ -833,9 +745,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact and subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
|
||||
// The message is not in the database
|
||||
assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
|
||||
@@ -853,7 +765,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact with a subscription
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
|
||||
// There's no local subscription for the group
|
||||
assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
|
||||
@@ -871,9 +783,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// There's no contact subscription for the group
|
||||
@@ -892,9 +804,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addGroup(txn, group);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
|
||||
// The subscription is not visible
|
||||
@@ -913,10 +825,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// The message has already been seen by the contact
|
||||
db.addStatus(txn, contactId, messageId, true);
|
||||
@@ -936,10 +848,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// The message has not been seen by the contact
|
||||
db.addStatus(txn, contactId, messageId, false);
|
||||
@@ -958,7 +870,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact and subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
|
||||
@@ -976,59 +888,59 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupMessageParentWithNoParent() throws Exception {
|
||||
public void testGetParentWithNoParent() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// A message with no parent should return null
|
||||
MessageId childId = new MessageId(TestUtils.getRandomId());
|
||||
Message child = new TestMessage(childId, null, group, null, contentType,
|
||||
subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, child, false);
|
||||
db.addMessage(txn, child, false);
|
||||
assertTrue(db.containsMessage(txn, childId));
|
||||
assertNull(db.getGroupMessageParent(txn, childId));
|
||||
assertNull(db.getParent(txn, childId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupMessageParentWithAbsentParent() throws Exception {
|
||||
public void testGetParentWithAbsentParent() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// A message with an absent parent should return null
|
||||
MessageId childId = new MessageId(TestUtils.getRandomId());
|
||||
MessageId parentId = new MessageId(TestUtils.getRandomId());
|
||||
Message child = new TestMessage(childId, parentId, group, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, child, false);
|
||||
db.addMessage(txn, child, false);
|
||||
assertTrue(db.containsMessage(txn, childId));
|
||||
assertFalse(db.containsMessage(txn, parentId));
|
||||
assertNull(db.getGroupMessageParent(txn, childId));
|
||||
assertNull(db.getParent(txn, childId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupMessageParentWithParentInAnotherGroup()
|
||||
public void testGetParentWithParentInAnotherGroup()
|
||||
throws Exception {
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
Group group1 = new Group(groupId1, "Another group",
|
||||
new byte[GROUP_SALT_LENGTH]);
|
||||
new byte[GROUP_SALT_LENGTH], false);
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to two groups
|
||||
db.addSubscription(txn, group);
|
||||
db.addSubscription(txn, group1);
|
||||
db.addGroup(txn, group);
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// A message with a parent in another group should return null
|
||||
MessageId childId = new MessageId(TestUtils.getRandomId());
|
||||
@@ -1037,48 +949,24 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
contentType, subject, timestamp, raw);
|
||||
Message parent = new TestMessage(parentId, null, group1, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, child, false);
|
||||
db.addGroupMessage(txn, parent, false);
|
||||
db.addMessage(txn, child, false);
|
||||
db.addMessage(txn, parent, false);
|
||||
assertTrue(db.containsMessage(txn, childId));
|
||||
assertTrue(db.containsMessage(txn, parentId));
|
||||
assertNull(db.getGroupMessageParent(txn, childId));
|
||||
assertNull(db.getParent(txn, childId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupMessageParentWithPrivateParent() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
|
||||
// A message with a private parent should return null
|
||||
MessageId childId = new MessageId(TestUtils.getRandomId());
|
||||
Message child = new TestMessage(childId, messageId1, group, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, child, false);
|
||||
db.addPrivateMessage(txn, privateMessage, contactId, false);
|
||||
assertTrue(db.containsMessage(txn, childId));
|
||||
assertTrue(db.containsMessage(txn, messageId1));
|
||||
assertNull(db.getGroupMessageParent(txn, childId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupMessageParentWithParentInSameGroup()
|
||||
public void testGetParentWithParentInSameGroup()
|
||||
throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// A message with a parent in the same group should return the parent
|
||||
MessageId childId = new MessageId(TestUtils.getRandomId());
|
||||
@@ -1087,11 +975,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
contentType, subject, timestamp, raw);
|
||||
Message parent = new TestMessage(parentId, null, group, null,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, child, false);
|
||||
db.addGroupMessage(txn, parent, false);
|
||||
db.addMessage(txn, child, false);
|
||||
db.addMessage(txn, parent, false);
|
||||
assertTrue(db.containsMessage(txn, childId));
|
||||
assertTrue(db.containsMessage(txn, parentId));
|
||||
assertEquals(parentId, db.getGroupMessageParent(txn, childId));
|
||||
assertEquals(parentId, db.getParent(txn, childId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1105,16 +993,17 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact and subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// Store a couple of messages
|
||||
int bodyLength = raw.length - 20;
|
||||
Message message1 = new TestMessage(messageId, null, group, null,
|
||||
Message message = new TestMessage(messageId, null, group, null,
|
||||
contentType, subject, timestamp, raw, 5, bodyLength);
|
||||
Message privateMessage1 = new TestMessage(messageId1, null, null,
|
||||
null, contentType, subject, timestamp, raw, 10, bodyLength);
|
||||
db.addGroupMessage(txn, message1, false);
|
||||
db.addPrivateMessage(txn, privateMessage1, contactId, false);
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new TestMessage(messageId1, null, group, null,
|
||||
contentType, subject, timestamp, raw, 10, bodyLength);
|
||||
db.addMessage(txn, message, false);
|
||||
db.addMessage(txn, message1, false);
|
||||
|
||||
// Calculate the expected message bodies
|
||||
byte[] expectedBody = new byte[bodyLength];
|
||||
@@ -1144,27 +1033,27 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// Store a couple of messages
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addMessage(txn, message, false);
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId parentId = new MessageId(TestUtils.getRandomId());
|
||||
long timestamp1 = System.currentTimeMillis();
|
||||
Message message1 = new TestMessage(messageId1, parentId, group, author,
|
||||
contentType, subject, timestamp1, raw);
|
||||
db.addGroupMessage(txn, message1, false);
|
||||
db.addMessage(txn, message1, false);
|
||||
// Mark one of the messages read
|
||||
assertFalse(db.setReadFlag(txn, messageId, true));
|
||||
|
||||
// Retrieve the message headers
|
||||
Collection<GroupMessageHeader> headers =
|
||||
db.getGroupMessageHeaders(txn, groupId);
|
||||
Iterator<GroupMessageHeader> it = headers.iterator();
|
||||
Collection<MessageHeader> headers =
|
||||
db.getMessageHeaders(txn, groupId);
|
||||
Iterator<MessageHeader> it = headers.iterator();
|
||||
boolean messageFound = false, message1Found = false;
|
||||
// First header (order is undefined)
|
||||
assertTrue(it.hasNext());
|
||||
GroupMessageHeader header = it.next();
|
||||
MessageHeader header = it.next();
|
||||
if(messageId.equals(header.getId())) {
|
||||
assertHeadersMatch(message, header);
|
||||
assertTrue(header.isRead());
|
||||
@@ -1199,7 +1088,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
private void assertHeadersMatch(Message m, GroupMessageHeader h) {
|
||||
private void assertHeadersMatch(Message m, MessageHeader h) {
|
||||
assertEquals(m.getId(), h.getId());
|
||||
if(m.getParent() == null) assertNull(h.getParent());
|
||||
else assertEquals(m.getParent(), h.getParent());
|
||||
@@ -1216,8 +1105,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group and store a message
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, false);
|
||||
|
||||
// The message should be unread by default
|
||||
assertFalse(db.getReadFlag(txn, messageId));
|
||||
@@ -1230,7 +1119,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
assertTrue(db.setReadFlag(txn, messageId, false));
|
||||
assertFalse(db.setReadFlag(txn, messageId, false));
|
||||
// Unsubscribe from the group
|
||||
db.removeSubscription(txn, groupId);
|
||||
db.removeGroup(txn, groupId);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1242,24 +1131,24 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a couple of groups
|
||||
db.addSubscription(txn, group);
|
||||
db.addGroup(txn, group);
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
Group group1 = new Group(groupId1, "Another group",
|
||||
new byte[GROUP_SALT_LENGTH]);
|
||||
db.addSubscription(txn, group1);
|
||||
new byte[GROUP_SALT_LENGTH], false);
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// Store two messages in the first group
|
||||
db.addGroupMessage(txn, message, false);
|
||||
db.addMessage(txn, message, false);
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new TestMessage(messageId1, null, group, author,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, message1, false);
|
||||
db.addMessage(txn, message1, false);
|
||||
|
||||
// Store one message in the second group
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
Message message2 = new TestMessage(messageId2, null, group1, author,
|
||||
contentType, subject, timestamp, raw);
|
||||
db.addGroupMessage(txn, message2, false);
|
||||
db.addMessage(txn, message2, false);
|
||||
|
||||
// Mark one of the messages in the first group read
|
||||
assertFalse(db.setReadFlag(txn, messageId, true));
|
||||
@@ -1299,7 +1188,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
for(int i = 0; i < 100; i++) {
|
||||
GroupId id = new GroupId(TestUtils.getRandomId());
|
||||
String name = "Group " + i;
|
||||
groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH]));
|
||||
groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH], false));
|
||||
}
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1308,7 +1197,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Add a contact and subscribe to the groups
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
for(Group g : groups) db.addSubscription(txn, g);
|
||||
for(Group g : groups) db.addGroup(txn, g);
|
||||
|
||||
// Make the groups visible to the contact
|
||||
Collections.shuffle(groups);
|
||||
@@ -1319,7 +1208,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
for(Group g : groups) {
|
||||
if(Math.random() < 0.5)
|
||||
db.removeVisibility(txn, contactId, g.getId());
|
||||
db.removeSubscription(txn, g.getId());
|
||||
db.removeGroup(txn, g.getId());
|
||||
}
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -1644,11 +1533,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
assertEquals(contactId1, db.addContact(txn, author1, localAuthorId));
|
||||
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setSubscriptions(txn, contactId1, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
||||
db.setGroups(txn, contactId1, Arrays.asList(group), 1);
|
||||
|
||||
// The group should be available, not subscribed, not visible to all
|
||||
assertEquals(Collections.emptyList(), db.getSubscriptions(txn));
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||
Iterator<GroupStatus> it = db.getAvailableGroups(txn).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
GroupStatus status = it.next();
|
||||
@@ -1659,8 +1548,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// Subscribe to the group - it should be available, subscribed,
|
||||
// not visible to all
|
||||
db.addSubscription(txn, group);
|
||||
assertEquals(Arrays.asList(group), db.getSubscriptions(txn));
|
||||
db.addGroup(txn, group);
|
||||
assertEquals(Arrays.asList(group), db.getGroups(txn));
|
||||
it = db.getAvailableGroups(txn).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
status = it.next();
|
||||
@@ -1671,8 +1560,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// The first contact unsubscribes - the group should be available,
|
||||
// subscribed, not visible to all
|
||||
db.setSubscriptions(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Arrays.asList(group), db.getSubscriptions(txn));
|
||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Arrays.asList(group), db.getGroups(txn));
|
||||
it = db.getAvailableGroups(txn).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
status = it.next();
|
||||
@@ -1684,7 +1573,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
// Make the group visible to all contacts - it should be available,
|
||||
// subscribed, visible to all
|
||||
db.setVisibleToAll(txn, groupId, true);
|
||||
assertEquals(Arrays.asList(group), db.getSubscriptions(txn));
|
||||
assertEquals(Arrays.asList(group), db.getGroups(txn));
|
||||
it = db.getAvailableGroups(txn).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
status = it.next();
|
||||
@@ -1695,8 +1584,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// Unsubscribe from the group - it should be available, not subscribed,
|
||||
// not visible to all
|
||||
db.removeSubscription(txn, groupId);
|
||||
assertEquals(Collections.emptyList(), db.getSubscriptions(txn));
|
||||
db.removeGroup(txn, groupId);
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||
it = db.getAvailableGroups(txn).iterator();
|
||||
assertTrue(it.hasNext());
|
||||
status = it.next();
|
||||
@@ -1707,8 +1596,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
// The second contact unsubscribes - the group should no longer be
|
||||
// available
|
||||
db.setSubscriptions(txn, contactId1, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Collections.emptyList(), db.getSubscriptions(txn));
|
||||
db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||
assertEquals(Collections.emptyList(), db.getAvailableGroups(txn));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
|
||||
@@ -121,7 +121,7 @@ public class ConstantsTest extends BriarTestCase {
|
||||
MessageId parent = new MessageId(TestUtils.getRandomId());
|
||||
// Create a maximum-length group
|
||||
String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
Group group = groupFactory.createGroup(groupName);
|
||||
Group group = groupFactory.createGroup(groupName, false);
|
||||
// Create a maximum-length author
|
||||
String authorName =
|
||||
TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
@@ -177,13 +177,13 @@ public class ConstantsTest extends BriarTestCase {
|
||||
@Test
|
||||
public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
|
||||
// Create the maximum number of maximum-length groups
|
||||
Collection<Group> subs = new ArrayList<Group>();
|
||||
Collection<Group> groups = new ArrayList<Group>();
|
||||
for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
||||
String name = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
subs.add(groupFactory.createGroup(name));
|
||||
groups.add(groupFactory.createGroup(name, false));
|
||||
}
|
||||
// Create a maximum-length subscription update
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE);
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(groups, Long.MAX_VALUE);
|
||||
// Serialise the update
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.sf.briar.messaging.simplex;
|
||||
|
||||
import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -21,7 +22,9 @@ import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
||||
import net.sf.briar.api.db.event.MessageAddedEvent;
|
||||
import net.sf.briar.api.messaging.Group;
|
||||
import net.sf.briar.api.messaging.GroupId;
|
||||
import net.sf.briar.api.messaging.Message;
|
||||
import net.sf.briar.api.messaging.MessageFactory;
|
||||
import net.sf.briar.api.messaging.MessageVerifier;
|
||||
@@ -57,6 +60,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
private final Group group;
|
||||
private final TransportId transportId;
|
||||
private final byte[] initialSecret;
|
||||
private final long epoch;
|
||||
@@ -64,6 +68,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
private Injector alice, bob;
|
||||
|
||||
public SimplexMessagingIntegrationTest() throws Exception {
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], true);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
// Create matching secrets for Alice and Bob
|
||||
initialSecret = new byte[32];
|
||||
@@ -116,6 +122,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
Author bobAuthor = new Author(bobId, "Bob",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = db.addContact(bobAuthor, aliceId);
|
||||
// Add the inbox group
|
||||
db.addGroup(group);
|
||||
db.setInboxGroup(contactId, group);
|
||||
// Add the transport and the endpoint
|
||||
db.addTransport(transportId, LATENCY);
|
||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
|
||||
@@ -126,9 +135,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] body = "Hi Bob!".getBytes("UTF-8");
|
||||
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
|
||||
Message message = messageFactory.createPrivateMessage(null, contentType,
|
||||
timestamp, body);
|
||||
db.addLocalPrivateMessage(message, contactId);
|
||||
Message message = messageFactory.createAnonymousMessage(null, group,
|
||||
contentType, timestamp, body);
|
||||
db.addLocalMessage(message);
|
||||
// Create an outgoing simplex connection
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionRegistry connRegistry =
|
||||
@@ -172,6 +181,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
Author aliceAuthor = new Author(aliceId, "Alice",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = db.addContact(aliceAuthor, bobId);
|
||||
// Add the inbox group
|
||||
db.addGroup(group);
|
||||
db.setInboxGroup(contactId, group);
|
||||
// Add the transport and the endpoint
|
||||
db.addTransport(transportId, LATENCY);
|
||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
|
||||
@@ -227,7 +239,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
private boolean messageAdded = false;
|
||||
|
||||
public void eventOccurred(DatabaseEvent e) {
|
||||
if(e instanceof PrivateMessageAddedEvent) messageAdded = true;
|
||||
if(e instanceof MessageAddedEvent) messageAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user