Compare commits

..

14 Commits

Author SHA1 Message Date
Michael Rogers
3800cd5e4f Bumped version number for beta release. 2017-07-28 11:17:09 +01:00
akwizgran
259f2cd419 Merge branch '993-fix-full-text-blog-posts' into 'master'
Show blog posts with full text when clicked

Closes #993

See merge request !570
2017-07-26 11:01:38 +00:00
Torsten Grote
20eb022c36 Show blog posts with full text when clicked
This fixes a regression that was introduced in !551.
2017-07-25 15:50:04 -03:00
akwizgran
531e555b52 Bumped version number for beta release. 2017-07-25 18:43:19 +01:00
akwizgran
a9024aa34b Merge branch '955-shared-with-update' into 'master'
Fix "shared with" counter not being updated

Closes #955

See merge request !569
2017-07-25 17:40:40 +00:00
akwizgran
d4e3b7842c Merge branch 'blog-sharing-tests' into 'master'
Add unit tests for BlogSharingManager

See merge request !567
2017-07-25 17:40:29 +00:00
Torsten Grote
167fddfbcc Add unit tests for BlogSharingManager 2017-07-25 12:45:36 -03:00
Torsten Grote
a48d642648 Fix UI bug in CreateForumActivity and adapt group creation 2017-07-25 12:32:53 -03:00
Torsten Grote
9a70f054c7 Use proper GroupId when reacting to accepted invitations
Fixes #955
2017-07-25 10:03:13 -03:00
Torsten Grote
ca43d13bd6 Merge branch 'inject-properties-module-eager-singletons' into 'master'
Inject properties module's eager singletons

See merge request !568
2017-07-25 12:55:59 +00:00
akwizgran
5b71004179 Inject properties module's eager singletons. 2017-07-25 13:49:15 +01:00
akwizgran
63befccdbf Bumped expiry time and version number for beta release. 2017-07-21 11:52:09 +01:00
akwizgran
4ecf7c02d0 Merge branch '979-duplicate-blog-session' into 'master'
Fix Blog Sharing Sessions

Closes #979

See merge request !566
2017-07-21 10:27:21 +00:00
Torsten Grote
f25badc18c Move responsibility for pre-sharing blogs to sharing manager
to have all the code related to that in one place,
so it is easier to maintain and to spot bugs.

This also checks that only blogs without an existing sharing session
are shared and initialized again.
It extends an existing test to catch the missing check.

This removes some debugging information from the previous commit
to not leak private information via the sharing sessions.

Fixes #979
2017-07-17 14:07:47 -03:00
21 changed files with 382 additions and 106 deletions

View File

@@ -54,6 +54,7 @@ public class BrambleCoreModule {
c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());
c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());

View File

@@ -78,8 +78,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 1601
versionName "0.16.1"
versionCode 1604
versionName "0.16.4"
applicationId "org.briarproject.briar.beta"
resValue "string", "app_package", "org.briarproject.briar.beta"
buildConfigField "String", "GitHash", "\"${getGitHash()}\""

View File

@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
*/
public interface BriarApplication {
// This build expires on 15 October 2017
long EXPIRY_DATE = 1508022000 * 1000L;
// This build expires on 21 October 2017
long EXPIRY_DATE = 1508544000 * 1000L;
AndroidComponent getApplicationComponent();

View File

@@ -60,8 +60,7 @@ abstract class BasePostFragment extends BaseFragment {
false);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view);
ui.setOnBlogPostClickListener(new OnBlogPostClickListener() {
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there

View File

@@ -106,7 +106,7 @@ class BlogControllerImpl extends BaseControllerImpl
BlogInvitationResponseReceivedEvent b =
(BlogInvitationResponseReceivedEvent) e;
InvitationResponse r = b.getResponse();
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
LOG.info("Blog invitation accepted");
onBlogInvitationAccepted(b.getContactId());
}

View File

@@ -23,8 +23,7 @@ class BlogPostAdapter
int viewType) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog_post, parent, false);
BlogPostViewHolder ui = new BlogPostViewHolder(v);
ui.setOnBlogPostClickListener(listener);
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
return ui;
}

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
@@ -47,12 +48,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final ImageView reblogButton;
private final TextView body;
private final ViewGroup commentContainer;
private final boolean fullText;
@Nullable
private OnBlogPostClickListener listener;
@NonNull
private final OnBlogPostClickListener listener;
BlogPostViewHolder(View v) {
BlogPostViewHolder(View v, boolean fullText,
@NonNull OnBlogPostClickListener listener) {
super(v);
this.fullText = fullText;
this.listener = listener;
ctx = v.getContext();
layout = (ViewGroup) v.findViewById(R.id.postLayout);
@@ -64,10 +69,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
(ViewGroup) v.findViewById(R.id.commentContainer);
}
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
this.listener = listener;
}
void setVisibility(int visibility) {
layout.setVisibility(visibility);
}
@@ -92,7 +93,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
if (item == null) return;
setTransitionName(item.getId());
if (listener != null) {
if (!fullText) {
layout.setClickable(true);
layout.setOnClickListener(new OnClickListener() {
@Override
@@ -111,7 +112,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624
if (listener != null && item.getHeader().getType() == POST) {
if (!fullText && item.getHeader().getType() == POST) {
author.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -124,7 +125,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// post body
Spanned bodyText = getSpanned(item.getBody());
if (listener == null) {
if (fullText) {
body.setText(bodyText);
body.setTextIsSelectable(true);
makeLinksClickable(body);
@@ -170,7 +171,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setAuthor(item.getAuthor());
reblogger.setAuthorStatus(item.getAuthorStatus());
reblogger.setDate(item.getTimestamp());
if (listener != null) {
if (!fullText) {
reblogger.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -200,7 +201,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// TODO make author clickable #624
body.setText(c.getComment());
if (listener == null) body.setTextIsSelectable(true);
if (fullText) body.setTextIsSelectable(true);
commentContainer.addView(v);
}

View File

@@ -161,7 +161,18 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
private ViewHolder(View v) {
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
});
input = (TextInputView) v.findViewById(R.id.inputText);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -43,10 +44,10 @@ public class CreateForumActivity extends BriarActivity {
private static final Logger LOG =
Logger.getLogger(CreateForumActivity.class.getName());
private TextInputLayout nameEntryLayout;
private EditText nameEntry;
private Button createForumButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -58,6 +59,8 @@ public class CreateForumActivity extends BriarActivity {
setContentView(R.layout.activity_create_forum);
nameEntryLayout =
(TextInputLayout) findViewById(R.id.createForumNameLayout);
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
nameEntry.addTextChangedListener(new TextWatcher() {
@@ -85,8 +88,6 @@ public class CreateForumActivity extends BriarActivity {
}
});
feedback = (TextView) findViewById(R.id.createForumFeedback);
createForumButton = (Button) findViewById(R.id.createForumButton);
createForumButton.setOnClickListener(new OnClickListener() {
@Override
@@ -118,10 +119,10 @@ public class CreateForumActivity extends BriarActivity {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_FORUM_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
nameEntryLayout.setError(getString(R.string.name_too_long));
return false;
}
feedback.setText("");
nameEntryLayout.setError(null);
return length > 0;
}

View File

@@ -85,7 +85,7 @@ class ForumControllerImpl extends
(ForumInvitationResponseReceivedEvent) e;
ForumInvitationResponse r =
(ForumInvitationResponse) f.getResponse();
if (r.getGroupId().equals(getGroupId()) && r.wasAccepted()) {
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
LOG.info("Forum invitation was accepted");
onForumInvitationAccepted(r.getContactId());
}

View File

@@ -108,7 +108,7 @@ class GroupControllerImpl extends
(GroupInvitationResponseReceivedEvent) e;
final GroupInvitationResponse r =
(GroupInvitationResponse) g.getResponse();
if (getGroupId().equals(r.getGroupId()) && r.wasAccepted()) {
if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Context;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -11,6 +12,7 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -19,6 +21,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
public class CreateGroupFragment extends BaseFragment {
@@ -28,7 +32,8 @@ public class CreateGroupFragment extends BaseFragment {
private CreateGroupListener listener;
private EditText nameEntry;
private Button createGroupButton;
private TextView feedback;
private TextInputLayout nameLayout;
private ProgressBar progress;
@Override
public void onAttach(Context context) {
@@ -69,7 +74,7 @@ public class CreateGroupFragment extends BaseFragment {
}
});
feedback = (TextView) v.findViewById(R.id.feedback);
nameLayout = (TextInputLayout) v.findViewById(R.id.nameLayout);
createGroupButton = (Button) v.findViewById(R.id.button);
createGroupButton.setOnClickListener(new OnClickListener() {
@@ -79,6 +84,8 @@ public class CreateGroupFragment extends BaseFragment {
}
});
progress = (ProgressBar) v.findViewById(R.id.progressBar);
return v;
}
@@ -107,16 +114,18 @@ public class CreateGroupFragment extends BaseFragment {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_GROUP_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
nameLayout.setError(getString(R.string.name_too_long));
return false;
}
feedback.setText("");
nameLayout.setError(null);
return length > 0;
}
private void createGroup() {
if (!validateName()) return;
listener.hideSoftKeyboard(nameEntry);
createGroupButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
listener.onGroupNameChosen(nameEntry.getText().toString());
}
}

View File

@@ -1,37 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="@dimen/margin_large">
android:padding="@dimen/margin_large">
<EditText
<android.support.design.widget.TextInputLayout
android:id="@+id/createForumNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/createForumNameEntry"
android:maxLines="1"
android:inputType="text|textCapSentences"
android:hint="@string/choose_forum_hint" />
app:errorEnabled="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/createForumFeedback"
android:gravity="center" />
<EditText
android:id="@+id/createForumNameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/choose_forum_hint"
android:inputType="text|textCapSentences"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
style="@style/BriarButton"
android:id="@+id/createForumButton"
style="@style/BriarButton"
android:enabled="false"
android:text="@string/create_forum_button" />
android:text="@string/create_forum_button"/>
<ProgressBar
android:id="@+id/createForumProgressBar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

View File

@@ -1,30 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="@dimen/margin_large">
android:padding="@dimen/margin_large">
<EditText
android:id="@+id/name"
<android.support.design.widget.TextInputLayout
android:id="@+id/nameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:inputType="text|textCapSentences"
android:hint="@string/groups_create_group_hint"/>
app:errorEnabled="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/feedback"
android:gravity="center" />
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/groups_create_group_hint"
android:inputType="text|textCapSentences"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
style="@style/BriarButton"
android:id="@+id/button"
style="@style/BriarButton"
android:enabled="false"
android:text="@string/groups_create_group_button"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

View File

@@ -48,9 +48,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
import static org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
@@ -72,7 +70,7 @@ import static org.briarproject.briar.blog.BlogPostValidator.authorToBdfDictionar
@NotNullByDefault
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
AddContactHook, RemoveContactHook, Client {
RemoveContactHook, Client {
private final IdentityManager identityManager;
private final BlogFactory blogFactory;
@@ -96,26 +94,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
// Create our personal blog if necessary
LocalAuthor a = identityManager.getLocalAuthor(txn);
Blog b = blogFactory.createBlog(a);
db.addGroup(txn, b.getGroup());
// Ensure that we have the personal blogs of all contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Add the personal blog of the contact and share it with the contact
Blog b = blogFactory.createBlog(c.getAuthor());
addBlog(txn, b);
db.setGroupVisibility(txn, c.getId(), b.getId(), SHARED);
// Share our personal blog with the contact
LocalAuthor a = identityManager.getLocalAuthor(txn);
Blog b2 = blogFactory.createBlog(a);
db.setGroupVisibility(txn, c.getId(), b2.getId(), SHARED);
db.addGroup(txn, b.getGroup()); // does nothing, if group exists
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
Blog b = blogFactory.createBlog(c.getAuthor());
// TODO we might want to reconsider removing b, if otherwise shared
if (db.containsGroup(txn, b.getId())) removeBlog(txn, b);
}

View File

@@ -38,7 +38,6 @@ public class BlogModule {
ValidationManager validationManager) {
lifecycleManager.registerClient(blogManager);
contactManager.registerAddContactHook(blogManager);
contactManager.registerRemoveContactHook(blogManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, blogManager);
return blogManager;

View File

@@ -433,7 +433,6 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
// build post body
StringBuilder b = new StringBuilder();
b.append("<h3>").append(feed.getTitle()).append("</h3>");
if (!StringUtils.isNullOrEmpty(entry.getTitle())) {
b.append("<h1>").append(entry.getTitle()).append("</h1>");

View File

@@ -51,15 +51,23 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog>
return CLIENT_ID;
}
/**
* This is called during each startup for each existing Contact.
*/
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// creates a group to share with the contact
super.addingContact(txn, c);
// get our blog and that of Contact c
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
Blog ourBlog = blogManager.getPersonalBlog(localAuthor);
Blog theirBlog = blogManager.getPersonalBlog(c.getAuthor());
// pre-share both blogs, if they have not been shared already
try {
initializeSharedSession(txn, c, ourBlog);
initializeSharedSession(txn, c, theirBlog);
preShareShareable(txn, c, ourBlog);
preShareShareable(txn, c, theirBlog);
} catch (FormatException e) {
throw new DbException(e);
}

View File

@@ -139,9 +139,24 @@ abstract class SharingManagerImpl<S extends Shareable>
return false;
}
void initializeSharedSession(Transaction txn, Contact c, S shareable)
/**
* Adds the given Shareable and initializes a session between us
* and the Contact c in state SHARING.
* If a session already exists, this does nothing.
*/
void preShareShareable(Transaction txn, Contact c, S shareable)
throws DbException, FormatException {
// return if a session already exists with that Contact
GroupId contactGroupId = getContactGroup(c).getId();
StoredSession existingSession = getSession(txn, contactGroupId,
getSessionId(shareable.getId()));
if (existingSession != null) return;
// add and shares the shareable with the Contact
db.addGroup(txn, shareable.getGroup());
db.setGroupVisibility(txn, c.getId(), shareable.getId(), SHARED);
// initialize session in sharing state
Session session = new Session(SHARING, contactGroupId,
shareable.getId(), null, null, 0, 0);
MessageId storageId = createStorageId(txn, contactGroupId);

View File

@@ -31,15 +31,11 @@ import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
@@ -117,29 +113,12 @@ public class BlogManagerImplTest extends BriarTestCase {
public void testCreateLocalState() throws DbException {
final Transaction txn = new Transaction(null, false);
final ContactId contactId = new ContactId(0);
Contact contact = new Contact(contactId, blog2.getAuthor(),
blog1.getAuthor().getId(), true, true);
final Collection<Contact> contacts = Collections.singletonList(contact);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).addGroup(txn, blog1.getGroup());
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(blogFactory).createBlog(blog2.getAuthor());
will(returnValue(blog2));
oneOf(db).addGroup(txn, blog2.getGroup());
oneOf(db).setGroupVisibility(txn, contactId, blog2.getId(), SHARED);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).setGroupVisibility(txn, contactId, blog1.getId(), SHARED);
}});
blogManager.createLocalState(txn);

View File

@@ -0,0 +1,253 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogInvitationResponse;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
public class BlogSharingManagerImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final BlogSharingManagerImpl blogSharingManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final SessionEncoder sessionEncoder =
context.mock(SessionEncoder.class);
private final SessionParser sessionParser =
context.mock(SessionParser.class);
private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class);
private final BlogManager blogManager = context.mock(BlogManager.class);
private final AuthorId localAuthorId = new AuthorId(getRandomId());
private final ContactId contactId = new ContactId(0);
private final AuthorId authorId = new AuthorId(getRandomId());
private final Author author = new Author(authorId, "Author",
getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
private final Contact contact =
new Contact(contactId, author, localAuthorId, true, true);
private final Collection<Contact> contacts =
Collections.singletonList(contact);
private final Group contactGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID,
getRandomBytes(42));
private final Group blogGroup =
new Group(new GroupId(getRandomId()), BlogManager.CLIENT_ID,
getRandomBytes(42));
private final Blog blog = new Blog(blogGroup, author, false);
@SuppressWarnings("unchecked")
private final ProtocolEngine<Blog> engine =
context.mock(ProtocolEngine.class);
@SuppressWarnings("unchecked")
public BlogSharingManagerImplTest() {
MetadataParser metadataParser = context.mock(MetadataParser.class);
MessageTracker messageTracker = context.mock(MessageTracker.class);
MessageParser<Blog> messageParser = context.mock(MessageParser.class);
InvitationFactory<Blog, BlogInvitationResponse> invitationFactory =
context.mock(InvitationFactory.class);
blogSharingManager =
new BlogSharingManagerImpl(db, clientHelper, metadataParser,
messageParser, sessionEncoder, sessionParser,
messageTracker, contactGroupFactory,
engine, invitationFactory, identityManager,
blogManager);
}
@Test
public void testAddingContactFreshState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(0);
testAddingContact(sessions);
}
@Test
public void testAddingContactExistingState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(1);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testAddingContact(sessions);
}
@Test(expected = DbException.class)
public void testAddingContactMultipleSessions() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(2);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testAddingContact(sessions);
}
@Test
public void testRemovingBlogFreshState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(0);
testRemovingBlog(sessions);
}
@Test
public void testRemovingBlogExistingState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(1);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testRemovingBlog(sessions);
}
@Test(expected = DbException.class)
public void testRemovingBlogMultipleSessions() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(2);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testRemovingBlog(sessions);
}
private void testAddingContact(final Map<MessageId, BdfDictionary> sessions)
throws Exception {
final Transaction txn = new Transaction(null, false);
final LocalAuthor localAuthor =
new LocalAuthor(localAuthorId, "Local Author",
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
System.currentTimeMillis());
final BdfDictionary meta = BdfDictionary
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, contactId.getInt()));
final Group localBlogGroup =
new Group(new GroupId(getRandomId()), BlogManager.CLIENT_ID,
getRandomBytes(42));
final Blog localBlog = new Blog(localBlogGroup, localAuthor, false);
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contactId, contactGroup.getId(),
SHARED);
oneOf(clientHelper)
.mergeGroupMetadata(txn, contactGroup.getId(), meta);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(blogManager).getPersonalBlog(localAuthor);
will(returnValue(localBlog));
oneOf(blogManager).getPersonalBlog(author);
will(returnValue(blog));
}});
expectPreShareShareable(txn, contact, localBlog, sessions);
expectPreShareShareable(txn, contact, blog, sessions);
blogSharingManager.createLocalState(txn);
}
private void expectPreShareShareable(final Transaction txn,
final Contact contact, final Blog blog,
final Map<MessageId, BdfDictionary> sessions) throws Exception {
final Group contactGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID,
getRandomBytes(42));
final BdfDictionary sessionDict = new BdfDictionary();
final Message message =
new Message(new MessageId(getRandomId()), contactGroup.getId(),
42L, getRandomBytes(1337));
context.checking(new Expectations() {{
oneOf(contactGroupFactory)
.createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.getSessionQuery(new SessionId(blog.getId().getBytes()));
will(returnValue(sessionDict));
oneOf(clientHelper)
.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
sessionDict);
will(returnValue(sessions));
if (sessions.size() == 0) {
oneOf(db).addGroup(txn, blog.getGroup());
oneOf(db).setGroupVisibility(txn, contact.getId(),
blog.getGroup().getId(), SHARED);
oneOf(clientHelper)
.createMessageForStoringMetadata(contactGroup.getId());
will(returnValue(message));
oneOf(db).addLocalMessage(txn, message, new Metadata(), false);
oneOf(sessionEncoder).encodeSession(with(any(Session.class)));
will(returnValue(sessionDict));
oneOf(clientHelper).mergeMessageMetadata(txn, message.getId(),
sessionDict);
}
}});
}
private void testRemovingBlog(final Map<MessageId, BdfDictionary> sessions)
throws Exception {
final Transaction txn = new Transaction(null, false);
final BdfDictionary sessionDict = new BdfDictionary();
final Session session = new Session(contactGroup.getId(), blog.getId());
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.getSessionQuery(new SessionId(blog.getId().getBytes()));
will(returnValue(sessionDict));
oneOf(clientHelper)
.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
sessionDict);
will(returnValue(sessions));
if (sessions.size() == 1) {
oneOf(sessionParser)
.parseSession(contactGroup.getId(), sessionDict);
will(returnValue(session));
oneOf(engine).onLeaveAction(txn, session);
will(returnValue(session));
oneOf(sessionEncoder).encodeSession(session);
will(returnValue(sessionDict));
oneOf(clientHelper).mergeMessageMetadata(txn,
sessions.keySet().iterator().next(), sessionDict);
}
}});
blogSharingManager.removingBlog(txn, blog);
}
}