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