diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java index 7e89a7a7bf17c8ae7cb4eb78576c54a4ece1f720..b7c208ff29feb9bafe1a67d47643ee28077f8d18 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java @@ -13,7 +13,9 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class Author { - public enum Status {ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES} + public enum Status { + NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES + } private final AuthorId id; private final String name; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 54238266b28389ad1146758590d6c441607c4046..7ae01fe47a056d4d1b7cf361ab3596032e52b25a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -68,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; @NotNullByDefault abstract class JdbcDatabase implements Database<Connection> { - private static final int SCHEMA_VERSION = 29; - private static final int MIN_SCHEMA_VERSION = 29; + private static final int SCHEMA_VERSION = 30; + private static final int MIN_SCHEMA_VERSION = 30; private static final String CREATE_SETTINGS = "CREATE TABLE settings" diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSocksModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSocksModule.java new file mode 100644 index 0000000000000000000000000000000000000000..e142a24790e2348a92c759798a507ac81c70920e --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSocksModule.java @@ -0,0 +1,16 @@ +package org.briarproject.bramble.test; + +import javax.net.SocketFactory; + +import dagger.Module; +import dagger.Provides; + +@Module +public class TestSocksModule { + + @Provides + SocketFactory provideSocketFactory() { + return SocketFactory.getDefault(); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java index d8561ad4d0401e0256ae61c0be6f1c068db7511e..546f9ac18005508ef1e79de8cd539c6ccf05fe92 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java @@ -48,6 +48,10 @@ public class BlogPostItem implements Comparable<BlogPostItem> { return body; } + public boolean isRssFeed() { + return header.isRssFeed(); + } + public boolean isRead() { return read; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java index ba1d11e6832e2f319f4f7880260775ae0c9e83ed..76232fc5755fc52474c25f4203a2a2e7a1cbf196 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java @@ -108,7 +108,8 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { author.setAuthor(a); author.setAuthorStatus(post.getAuthorStatus()); author.setDate(post.getTimestamp()); - author.setPersona(AuthorView.NORMAL); + author.setPersona( + item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL); // TODO make author clickable more often #624 if (item.getHeader().getType() == POST) { author.setBlogLink(post.getGroupId()); @@ -168,7 +169,9 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { reblogger.setVisibility(VISIBLE); reblogger.setPersona(AuthorView.REBLOGGER); - author.setPersona(AuthorView.COMMENTER); + author.setPersona(item.getHeader().getRootPost().isRssFeed() ? + AuthorView.RSS_FEED_REBLOGGED : + AuthorView.COMMENTER); // comments for (BlogCommentHeader c : item.getComments()) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java index 82a4320918fa1e80ce1bc2c80742b71ec4322b96..c7a64659a68367487f6f630bce75e24b34e28f8b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java @@ -179,7 +179,6 @@ public class FeedFragment extends BaseFragment implements case R.id.action_rss_feeds_import: Intent i2 = new Intent(getActivity(), RssFeedImportActivity.class); - i2.putExtra(GROUP_ID, personalBlog.getId().getBytes()); startActivity(i2); return true; case R.id.action_rss_feeds_manage: diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedAdapter.java index 3052e3694f22d42ac3001d3c3e7208b93b0fc9b8..e4ed107b6a9686509956a6e2bc717f417763dcd0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedAdapter.java @@ -6,7 +6,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.ImageView; +import android.widget.ImageButton; import android.widget.TextView; import org.briarproject.briar.R; @@ -39,12 +39,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> { if (item == null) return; // Feed Title - if (item.getTitle() != null) { - ui.title.setText(item.getTitle()); - ui.title.setVisibility(VISIBLE); - } else { - ui.title.setVisibility(GONE); - } + ui.title.setText(item.getTitle()); // Delete Button ui.delete.setOnClickListener(new OnClickListener() { @@ -75,6 +70,14 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> { } else { ui.description.setVisibility(GONE); } + + // Open feed's blog when clicked + ui.layout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.onFeedClick(item); + } + }); } @Override @@ -99,8 +102,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> { } static class FeedViewHolder extends RecyclerView.ViewHolder { + private final View layout; private final TextView title; - private final ImageView delete; + private final ImageButton delete; private final TextView imported; private final TextView updated; private final TextView author; @@ -110,8 +114,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> { private FeedViewHolder(View v) { super(v); + layout = v; title = (TextView) v.findViewById(R.id.titleView); - delete = (ImageView) v.findViewById(R.id.deleteButton); + delete = (ImageButton) v.findViewById(R.id.deleteButton); imported = (TextView) v.findViewById(R.id.importedView); updated = (TextView) v.findViewById(R.id.updatedView); author = (TextView) v.findViewById(R.id.authorView); @@ -121,6 +126,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> { } interface RssFeedListener { + void onFeedClick(Feed feed); void onDeleteClick(Feed feed); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedImportActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedImportActivity.java index 7e2085818265acdf4dd09b2d556e726828438f3b..1d09ad2c428def1e37980d472cc8c587330bfd36 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedImportActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedImportActivity.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.blog; import android.content.DialogInterface; -import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.text.Editable; @@ -15,7 +14,6 @@ import android.widget.ProgressBar; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.IoExecutor; -import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -44,9 +42,6 @@ public class RssFeedImportActivity extends BriarActivity { @IoExecutor Executor ioExecutor; - // Fields that are accessed from background threads must be volatile - private volatile GroupId groupId = null; - @Inject @SuppressWarnings("WeakerAccess") volatile FeedManager feedManager; @@ -55,12 +50,6 @@ public class RssFeedImportActivity extends BriarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // GroupId from Intent - Intent i = getIntent(); - byte[] b = i.getByteArrayExtra(GROUP_ID); - if (b == null) throw new IllegalStateException("No Group in intent."); - groupId = new GroupId(b); - setContentView(R.layout.activity_rss_feed_import); urlInput = (EditText) findViewById(R.id.urlInput); @@ -128,7 +117,7 @@ public class RssFeedImportActivity extends BriarActivity { @Override public void run() { try { - feedManager.addFeed(url, groupId); + feedManager.addFeed(url); feedImported(); } catch (DbException | IOException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedManageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedManageActivity.java index 10e37d6f27d204d2756fd7438a854f7c4e033f7a..8525f1f653f5e1547612504624567664b1cea465 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedManageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/RssFeedManageActivity.java @@ -1,15 +1,16 @@ package org.briarproject.briar.android.blog; +import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -23,6 +24,7 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.support.design.widget.Snackbar.LENGTH_LONG; import static java.util.logging.Level.WARNING; @@ -34,7 +36,6 @@ public class RssFeedManageActivity extends BriarActivity private BriarRecyclerView list; private RssFeedAdapter adapter; - private GroupId groupId; @Inject @SuppressWarnings("WeakerAccess") @@ -44,12 +45,6 @@ public class RssFeedManageActivity extends BriarActivity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // GroupId from Intent - Intent i = getIntent(); - byte[] b = i.getByteArrayExtra(GROUP_ID); - if (b == null) throw new IllegalStateException("No Group in intent."); - groupId = new GroupId(b); - setContentView(R.layout.activity_rss_feed_manage); adapter = new RssFeedAdapter(this, this); @@ -87,7 +82,6 @@ public class RssFeedManageActivity extends BriarActivity return true; case R.id.action_rss_feeds_import: Intent i = new Intent(this, RssFeedImportActivity.class); - i.putExtra(GROUP_ID, groupId.getBytes()); startActivity(i); return true; default: @@ -100,21 +94,32 @@ public class RssFeedManageActivity extends BriarActivity component.inject(this); } + @Override + public void onFeedClick(Feed feed) { + Intent i = new Intent(this, BlogActivity.class); + i.putExtra(GROUP_ID, feed.getBlogId().getBytes()); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + } + @Override public void onDeleteClick(final Feed feed) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - feedManager.removeFeed(feed.getUrl()); - onFeedDeleted(feed); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - onDeleteError(); - } - } - }); + DialogInterface.OnClickListener okListener = + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteFeed(feed); + } + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this, + R.style.BriarDialogTheme); + builder.setTitle(getString(R.string.blogs_rss_remove_feed)); + builder.setMessage( + getString(R.string.blogs_rss_remove_feed_dialog_message)); + builder.setPositiveButton(R.string.cancel, null); + builder.setNegativeButton(R.string.blogs_rss_remove_feed_ok, + okListener); + builder.show(); } private void loadFeeds() { @@ -149,6 +154,22 @@ public class RssFeedManageActivity extends BriarActivity }); } + private void deleteFeed(final Feed feed) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + feedManager.removeFeed(feed); + onFeedDeleted(feed); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + onDeleteError(); + } + } + }); + } + private void onLoadError() { runOnUiThreadUnlessDestroyed(new Runnable() { @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/AuthorView.java b/briar-android/src/main/java/org/briarproject/briar/android/view/AuthorView.java index 82442185e6b1ddb1ff08a6a5f9f2da19dc30078c..f03a88ecd1d17277bae5c70994584a9abef77683 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/AuthorView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/AuthorView.java @@ -30,6 +30,7 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.graphics.Typeface.BOLD; import static android.util.TypedValue.COMPLEX_UNIT_PX; +import static org.briarproject.bramble.api.identity.Author.Status.NONE; import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; @@ -40,6 +41,8 @@ public class AuthorView extends RelativeLayout { public static final int REBLOGGER = 1; public static final int COMMENTER = 2; public static final int LIST = 3; + public static final int RSS_FEED = 4; + public static final int RSS_FEED_REBLOGGED = 5; private final CircleImageView avatar; private final ImageView avatarIcon; @@ -83,7 +86,13 @@ public class AuthorView extends RelativeLayout { } public void setAuthorStatus(Status status) { - trustIndicator.setTrustLevel(status); + if (status != NONE) { + trustIndicator.setTrustLevel(status); + trustIndicator.setVisibility(VISIBLE); + } else { + trustIndicator.setVisibility(GONE); + } + if (status == OURSELVES) { authorName.setTypeface(authorNameTypeface, BOLD); } else { @@ -124,10 +133,17 @@ public class AuthorView extends RelativeLayout { setOnClickListener(null); } + /** + * Styles this view for a different persona. + * + * Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar + * and override the one set by + * {@link AuthorView#setAuthor(Author)}. + */ public void setPersona(int persona) { switch (persona) { case NORMAL: - avatarIcon.setVisibility(VISIBLE); + avatarIcon.setVisibility(INVISIBLE); date.setVisibility(VISIBLE); setAvatarSize(R.dimen.blogs_avatar_normal_size); setTextSize(authorName, R.dimen.text_size_small); @@ -158,6 +174,24 @@ public class AuthorView extends RelativeLayout { setCenterVertical(authorName, true); setCenterVertical(trustIndicator, true); break; + case RSS_FEED: + avatarIcon.setVisibility(INVISIBLE); + date.setVisibility(VISIBLE); + avatar.setImageResource(R.drawable.ic_rss_feed); + setAvatarSize(R.dimen.blogs_avatar_normal_size); + setTextSize(authorName, R.dimen.text_size_small); + setCenterVertical(authorName, false); + setCenterVertical(trustIndicator, false); + break; + case RSS_FEED_REBLOGGED: + avatarIcon.setVisibility(INVISIBLE); + date.setVisibility(VISIBLE); + avatar.setImageResource(R.drawable.ic_rss_feed); + setAvatarSize(R.dimen.blogs_avatar_comment_size); + setTextSize(authorName, R.dimen.text_size_tiny); + setCenterVertical(authorName, false); + setCenterVertical(trustIndicator, false); + break; } } diff --git a/briar-android/src/main/res/drawable/ic_rss_feed.xml b/briar-android/src/main/res/drawable/ic_rss_feed.xml new file mode 100644 index 0000000000000000000000000000000000000000..6d441fa064cd0a237076a0142d60f145059e0623 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_rss_feed.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="30dp" + android:height="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + + <path + android:fillColor="#ffa500" + android:pathData="M0,8.88178e-16 L30,8.88178e-16 L30,30 L0,30 L0,8.88178e-16 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678 +C11.9661,22.7434,10.6078,24.1017,8.9322,24.1017 +C7.25663,24.1017,5.8983,22.7434,5.8983,21.0678 +C5.8983,19.3922,7.25663,18.0339,8.9322,18.0339 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M5.8983,15 A9.1016949,9.1016949,0,0,1,15,24.1017 L18.0339,24.1017 +A12.135593,12.135593,0,0,0,5.8983,11.9661 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M5.8983,8.9322 A15.169492,15.169492,0,0,1,21.0678,24.1017 L24.1017,24.1017 +A18.20339,18.20339,0,0,0,5.8983,5.8983 Z"/> +</vector> \ No newline at end of file diff --git a/briar-android/src/main/res/layout/list_item_rss_feed.xml b/briar-android/src/main/res/layout/list_item_rss_feed.xml index 76e0978a7719b623ca4b55d248eea69d87da8b14..183dfc3b0c4faea28ba30dc4e0ca7203c0052db9 100644 --- a/briar-android/src/main/res/layout/list_item_rss_feed.xml +++ b/briar-android/src/main/res/layout/list_item_rss_feed.xml @@ -19,7 +19,7 @@ android:textSize="@dimen/text_size_medium" tools:text="This is a name of a RSS Feed"/> - <ImageView + <ImageButton android:id="@+id/deleteButton" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/briar-android/src/main/res/values/attrs.xml b/briar-android/src/main/res/values/attrs.xml index 6d6207cd0b0843ee11b2a15f0c0ba2378a8ee350..8655d2126fc21b9c7a3fcc732e7c4ea30b7c0486 100644 --- a/briar-android/src/main/res/values/attrs.xml +++ b/briar-android/src/main/res/values/attrs.xml @@ -12,6 +12,8 @@ <enum name="reblogger" value="1"/> <enum name="commenter" value="2"/> <enum name="list" value="3"/> + <enum name="rss_feed" value="4"/> + <enum name="rss_feed_reblogged" value="5"/> </attr> </declare-styleable> diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 99e86b60e60929346467ce67fc34962e99de9a73..3c8a44883027d77467b7189afeea387410c2bb32 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -304,6 +304,9 @@ <string name="blogs_rss_feeds_manage_imported">Imported:</string> <string name="blogs_rss_feeds_manage_author">Author:</string> <string name="blogs_rss_feeds_manage_updated">Last Updated:</string> + <string name="blogs_rss_remove_feed">Remove Feed</string> + <string name="blogs_rss_remove_feed_dialog_message">Are you sure you want to remove this feed and all its posts?\nAny posts you have shared will not be removed from other people\'s devices.</string> + <string name="blogs_rss_remove_feed_ok">Remove Feed</string> <string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string> <string name="blogs_rss_feeds_manage_empty_state">You haven\'t imported any RSS feeds.\n\nWhy don\'t you click the plus in the top right screen corner to add your first?</string> <string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string> diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/Blog.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/Blog.java index b7e1d240fea96c14eed901a1fff18c2c9424dac9..a0e4fff021f700be77017977701959a79d2eca8a 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/Blog.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/Blog.java @@ -13,16 +13,22 @@ import javax.annotation.concurrent.Immutable; public class Blog extends BaseGroup implements Shareable { private final Author author; + private final boolean rssFeed; - public Blog(Group group, Author author) { + public Blog(Group group, Author author, boolean rssFeed) { super(group); this.author = author; + this.rssFeed = rssFeed; } public Author getAuthor() { return author; } + public boolean isRssFeed() { + return rssFeed; + } + @Override public boolean equals(Object o) { return o instanceof Blog && super.equals(o); diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogCommentHeader.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogCommentHeader.java index 70fded28135d661b93bcb7500bb42082cefe6b15..f7914c9d55a2c45329522ea1795010c64887b5a3 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogCommentHeader.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogCommentHeader.java @@ -26,7 +26,7 @@ public class BlogCommentHeader extends BlogPostHeader { Status authorStatus, boolean read) { super(type, groupId, id, parent.getId(), timestamp, - timeReceived, author, authorStatus, read); + timeReceived, author, authorStatus, false, read); if (type != COMMENT && type != WRAPPED_COMMENT) throw new IllegalArgumentException("Incompatible Message Type"); @@ -43,4 +43,11 @@ public class BlogCommentHeader extends BlogPostHeader { public BlogPostHeader getParent() { return parent; } + + public BlogPostHeader getRootPost() { + if (parent instanceof BlogCommentHeader) + return ((BlogCommentHeader) parent).getRootPost(); + return parent; + } + } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java index 99c4514b2639e51005df4459ad00d9ae28c11d86..4c8ff25d101c675ef0d92165a8b5ebf49708a954 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java @@ -28,6 +28,7 @@ public interface BlogConstants { String KEY_AUTHOR_NAME = "name"; String KEY_PUBLIC_KEY = "publicKey"; String KEY_AUTHOR = "author"; + String KEY_RSS_FEED = "rssFeed"; String KEY_READ = "read"; String KEY_COMMENT = "comment"; String KEY_ORIGINAL_MSG_ID = "originalMessageId"; diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogFactory.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogFactory.java index 4b0a89cf377b3f8c036cd206e1f15a8c78e9be4b..a9d057501da211b41d383b1cad7bf47f110e748f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogFactory.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogFactory.java @@ -13,6 +13,11 @@ public interface BlogFactory { */ Blog createBlog(Author author); + /** + * Creates a RSS feed blog for a given author. + */ + Blog createFeedBlog(Author author); + /** * Parses a blog with the given Group */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java index 20ffb991cac62a97aa50182af88299e2b5ff08fd..041d474a4eca490b8444c47a9feac2c652792800 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java @@ -41,6 +41,11 @@ public interface BlogManager { */ void removeBlog(Blog b) throws DbException; + /** + * Removes and deletes a blog with the given {@link Transaction}. + */ + void removeBlog(Transaction txn, Blog b) throws DbException; + /** * Stores a local blog post. */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostHeader.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostHeader.java index cf01dfc57117deb72e48df2ba304dd16f90d9162..57a0ef58a93968c690335bb53736f76678eb2518 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostHeader.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostHeader.java @@ -17,21 +17,23 @@ public class BlogPostHeader extends PostHeader { private final MessageType type; private final GroupId groupId; private final long timeReceived; + private final boolean rssFeed; public BlogPostHeader(MessageType type, GroupId groupId, MessageId id, @Nullable MessageId parentId, long timestamp, long timeReceived, - Author author, Status authorStatus, boolean read) { + Author author, Status authorStatus, boolean rssFeed, boolean read) { super(id, parentId, timestamp, author, authorStatus, read); this.type = type; this.groupId = groupId; this.timeReceived = timeReceived; + this.rssFeed = rssFeed; } public BlogPostHeader(MessageType type, GroupId groupId, MessageId id, long timestamp, long timeReceived, Author author, - Status authorStatus, boolean read) { + Status authorStatus, boolean rssFeed, boolean read) { this(type, groupId, id, null, timestamp, timeReceived, author, - authorStatus, read); + authorStatus, rssFeed, read); } public MessageType getType() { @@ -45,4 +47,9 @@ public class BlogPostHeader extends PostHeader { public long getTimeReceived() { return timeReceived; } + + public boolean isRssFeed() { + return rssFeed; + } + } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/feed/Feed.java b/briar-api/src/main/java/org/briarproject/briar/api/feed/Feed.java index b36a8a70b113a395aa58f213e3214b840c194a7c..f2fcaa38723832f478dfccc0f57f28698876f909 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/feed/Feed.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/feed/Feed.java @@ -1,40 +1,31 @@ package org.briarproject.briar.api.feed; -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.data.BdfDictionary; -import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.briar.api.blog.Blog; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_BLOG_GROUP_ID; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_ADDED; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_AUTHOR; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_DESC; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_LAST_ENTRY; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_TITLE; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_UPDATED; -import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_URL; - @Immutable @NotNullByDefault public class Feed { private final String url; - private final GroupId blogId; + private final Blog blog; + private final LocalAuthor localAuthor; @Nullable - private final String title, description, author; + private final String description, author; private final long added, updated, lastEntryTime; - public Feed(String url, GroupId blogId, @Nullable String title, - @Nullable String description, @Nullable String author, - long added, long updated, long lastEntryTime) { + public Feed(String url, Blog blog, LocalAuthor localAuthor, + @Nullable String description, @Nullable String author, long added, + long updated, long lastEntryTime) { this.url = url; - this.blogId = blogId; - this.title = title; + this.blog = blog; + this.localAuthor = localAuthor; this.description = description; this.author = author; this.added = added; @@ -42,13 +33,13 @@ public class Feed { this.lastEntryTime = lastEntryTime; } - public Feed(String url, GroupId blogId, @Nullable String title, + public Feed(String url, Blog blog, LocalAuthor localAuthor, @Nullable String description, @Nullable String author, long added) { - this(url, blogId, title, description, author, added, 0L, 0L); + this(url, blog, localAuthor, description, author, added, 0L, 0L); } - public Feed(String url, GroupId blogId, long added) { - this(url, blogId, null, null, null, added, 0L, 0L); + public Feed(String url, Blog blog, LocalAuthor localAuthor, long added) { + this(url, blog, localAuthor, null, null, added, 0L, 0L); } public String getUrl() { @@ -56,39 +47,19 @@ public class Feed { } public GroupId getBlogId() { - return blogId; + return blog.getId(); } - public BdfDictionary toBdfDictionary() { - BdfDictionary d = BdfDictionary.of( - new BdfEntry(KEY_FEED_URL, url), - new BdfEntry(KEY_BLOG_GROUP_ID, blogId.getBytes()), - new BdfEntry(KEY_FEED_ADDED, added), - new BdfEntry(KEY_FEED_UPDATED, updated), - new BdfEntry(KEY_FEED_LAST_ENTRY, lastEntryTime) - ); - if (title != null) d.put(KEY_FEED_TITLE, title); - if (description != null) d.put(KEY_FEED_DESC, description); - if (author != null) d.put(KEY_FEED_AUTHOR, author); - return d; + public Blog getBlog() { + return blog; } - public static Feed from(BdfDictionary d) throws FormatException { - String url = d.getString(KEY_FEED_URL); - GroupId blogId = new GroupId(d.getRaw(KEY_BLOG_GROUP_ID)); - String title = d.getOptionalString(KEY_FEED_TITLE); - String desc = d.getOptionalString(KEY_FEED_DESC); - String author = d.getOptionalString(KEY_FEED_AUTHOR); - long added = d.getLong(KEY_FEED_ADDED, 0L); - long updated = d.getLong(KEY_FEED_UPDATED, 0L); - long lastEntryTime = d.getLong(KEY_FEED_LAST_ENTRY, 0L); - return new Feed(url, blogId, title, desc, author, added, updated, - lastEntryTime); + public LocalAuthor getLocalAuthor() { + return localAuthor; } - @Nullable public String getTitle() { - return title; + return blog.getName(); } @Nullable @@ -118,20 +89,9 @@ public class Feed { if (this == o) return true; if (o instanceof Feed) { Feed f = (Feed) o; - return url.equals(f.url) && blogId.equals(f.getBlogId()) && - equalsWithNull(title, f.getTitle()) && - equalsWithNull(description, f.getDescription()) && - equalsWithNull(author, f.getAuthor()) && - added == f.getAdded() && - updated == f.getUpdated() && - lastEntryTime == f.getLastEntryTime(); + return blog.equals(f.blog); } return false; } - private boolean equalsWithNull(@Nullable Object a, @Nullable Object b) { - if (a == b) return true; - if (a == null || b == null) return false; - return a.equals(b); - } } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java index 8be05d5681fb7b1db2c26d71a484b4d5d69f2515..0fddb3aa73ed7ff3124f3984c1d2c82eec2f6387 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java @@ -18,8 +18,9 @@ public interface FeedConstants { // group metadata keys String KEY_FEEDS = "feeds"; String KEY_FEED_URL = "feedURL"; - String KEY_BLOG_GROUP_ID = "blogGroupId"; - String KEY_FEED_TITLE = "feedTitle"; + String KEY_BLOG_TITLE = "blogTitle"; + String KEY_PUBLIC_KEY = "publicKey"; + String KEY_PRIVATE_KEY = "privateKey"; String KEY_FEED_DESC = "feedDesc"; String KEY_FEED_AUTHOR = "feedAuthor"; String KEY_FEED_ADDED = "feedAdded"; diff --git a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java index 5d31b448a04295a369317b3601af005132dce707..7d83b22c151d70e37e69e7d04272001464b10a3b 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java @@ -3,7 +3,6 @@ package org.briarproject.briar.api.feed; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.ClientId; -import org.briarproject.bramble.api.sync.GroupId; import java.io.IOException; import java.util.List; @@ -17,14 +16,14 @@ public interface FeedManager { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed"); /** - * Adds an RSS feed. + * Adds an RSS feed as a new dedicated blog. */ - void addFeed(String url, GroupId g) throws DbException, IOException; + void addFeed(String url) throws DbException, IOException; /** * Removes an RSS feed. */ - void removeFeed(String url) throws DbException; + void removeFeed(Feed feed) throws DbException; /** * Returns a list of all added RSS feeds diff --git a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java index a9a7bfc506e344f4e400919327e29083d4e96c53..99d5879aa26e4f0657cce5f62a5221b252d09817 100644 --- a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java @@ -2,6 +2,7 @@ package org.briarproject.briar; import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; +import org.briarproject.briar.feed.DnsModule; import org.briarproject.briar.feed.FeedModule; import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.introduction.IntroductionModule; @@ -16,6 +17,7 @@ import dagger.Module; BlogModule.class, BriarClientModule.class, FeedModule.class, + DnsModule.class, ForumModule.class, GroupInvitationModule.class, IntroductionModule.class, diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java index 26c1eb108c0f9e75301e3e2a8f854adb6bc91660..c7d3a2001ab1a99cb8790ba700c8f3f59ef33f7e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java @@ -14,6 +14,9 @@ import org.briarproject.briar.api.blog.BlogFactory; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; + @Immutable @NotNullByDefault class BlogFactoryImpl implements BlogFactory { @@ -33,28 +36,46 @@ class BlogFactoryImpl implements BlogFactory { @Override public Blog createBlog(Author a) { + return createBlog(a, false); + } + + @Override + public Blog createFeedBlog(Author a) { + return createBlog(a, true); + } + + private Blog createBlog(Author a, boolean rssFeed) { try { BdfList blog = BdfList.of( a.getName(), - a.getPublicKey() + a.getPublicKey(), + rssFeed ); byte[] descriptor = clientHelper.toByteArray(blog); Group g = groupFactory .createGroup(BlogManagerImpl.CLIENT_ID, descriptor); - return new Blog(g, a); + return new Blog(g, a, rssFeed); } catch (FormatException e) { throw new RuntimeException(e); } } @Override - public Blog parseBlog(Group g) throws FormatException { - byte[] descriptor = g.getDescriptor(); + public Blog parseBlog(Group group) throws FormatException { + byte[] descriptor = group.getDescriptor(); // Author Name, Public Key BdfList blog = clientHelper.toList(descriptor); - Author a = - authorFactory.createAuthor(blog.getString(0), blog.getRaw(1)); - return new Blog(g, a); + String name = blog.getString(0); + if (name.length() > MAX_AUTHOR_NAME_LENGTH) + throw new IllegalArgumentException(); + byte[] publicKey = blog.getRaw(1); + if (publicKey.length > MAX_PUBLIC_KEY_LENGTH) + throw new IllegalArgumentException(); + + Author author = + authorFactory.createAuthor(name, publicKey); + boolean rssFeed = blog.getBoolean(2); + return new Blog(group, author, rssFeed); } } diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java index 496b308fb59513dfc948b0a5d21d7c02d5b98196..39ccb5241087c915669c032226f3e63d7de9c770 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java @@ -61,6 +61,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_ import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID; import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ; +import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; @@ -224,6 +225,11 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } + @Override + public void removeBlog(Transaction txn, Blog b) throws DbException { + removeBlog(txn, b, false); + } + private void removeBlog(Transaction txn, Blog b, boolean forced) throws DbException { if (!forced && !canBeRemoved(txn, b.getId())) @@ -248,15 +254,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, @Override public void addLocalPost(Transaction txn, BlogPost p) throws DbException { try { + GroupId groupId = p.getMessage().getGroupId(); + Blog b = getBlog(txn, groupId); + BdfDictionary meta = new BdfDictionary(); meta.put(KEY_TYPE, POST.getInt()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor())); meta.put(KEY_READ, true); + meta.put(KEY_RSS_FEED, b.isRssFeed()); clientHelper.addLocalMessage(txn, p.getMessage(), meta, true); // broadcast event about new post - GroupId groupId = p.getMessage().getGroupId(); MessageId postId = p.getMessage().getId(); BlogPostHeader h = getPostHeaderFromMetadata(txn, groupId, postId, meta); @@ -345,6 +354,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, wMessage = blogPostFactory .wrapPost(groupId, wDescriptor, wTimestamp, body); meta.put(KEY_TYPE, WRAPPED_POST.getInt()); + meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed()); } else if (type == COMMENT) { Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); byte[] wDescriptor = wGroup.getDescriptor(); @@ -593,8 +603,11 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, String name = d.getString(KEY_AUTHOR_NAME); byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY); Author author = new Author(authorId, name, publicKey); + boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false); Status authorStatus; - if (authorStatuses.containsKey(authorId)) { + if (isFeedPost) { + authorStatus = Status.NONE; + } else if (authorStatuses.containsKey(authorId)) { authorStatus = authorStatuses.get(authorId); } else { authorStatus = identityManager.getAuthorStatus(txn, authorId); @@ -611,7 +624,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, timestamp, timeReceived, author, authorStatus, read); } else { return new BlogPostHeader(type, groupId, id, timestamp, - timeReceived, author, authorStatus, read); + timeReceived, author, authorStatus, isFeedPost, read); } } diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java index 33fb0bddf76a4736df6bff337c7892f65520afca..5e74400c52ade433c2a175789d3e50648be6fbe7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java @@ -39,6 +39,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_ import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID; import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ; +import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; @@ -123,6 +124,7 @@ class BlogPostValidator extends BdfMessageValidator { BdfDictionary meta = new BdfDictionary(); meta.put(KEY_ORIGINAL_MSG_ID, m.getId()); meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); + meta.put(KEY_RSS_FEED, b.isRssFeed()); return new BdfMessageContext(meta); } diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/DnsModule.java b/briar-core/src/main/java/org/briarproject/briar/feed/DnsModule.java new file mode 100644 index 0000000000000000000000000000000000000000..dc6cd8a885d0b9740963b2647288c579a80c6ff3 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/feed/DnsModule.java @@ -0,0 +1,18 @@ +package org.briarproject.briar.feed; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Dns; + +/** + * This is a dedicated module, so it can be replaced for testing. + */ +@Module +public class DnsModule { + + @Provides + Dns provideDns(NoDns noDns) { + return noDns; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactory.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..192c4855eb47eccc8c21ae0275a4f94ca27cc6ea --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactory.java @@ -0,0 +1,34 @@ +package org.briarproject.briar.feed; + +import com.rometools.rome.feed.synd.SyndFeed; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.briar.api.feed.Feed; + +interface FeedFactory { + + /** + * Create a new feed based on the feed url + * and the metadata of an existing {@link SyndFeed}. + */ + Feed createFeed(String url, SyndFeed feed); + + /** + * Creates a new updated feed, based on the given existing feed, + * new metadata from the given {@link SyndFeed} + * and the time of the last feed entry. + */ + Feed createFeed(Feed feed, SyndFeed f, long lastEntryTime); + + /** + * De-serializes a {@link BdfDictionary} into a {@link Feed}. + */ + Feed createFeed(BdfDictionary d) throws FormatException; + + /** + * Serializes a {@link Feed} into a {@link BdfDictionary}. + */ + BdfDictionary feedToBdfDictionary(Feed feed); + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ec5d9c31b522cad7e335db12c4c373f984784afa --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java @@ -0,0 +1,112 @@ +package org.briarproject.briar.feed; + +import com.rometools.rome.feed.synd.SyndFeed; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.briar.api.blog.Blog; +import org.briarproject.briar.api.blog.BlogFactory; +import org.briarproject.briar.api.feed.Feed; + +import javax.inject.Inject; + +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_BLOG_TITLE; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_ADDED; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_AUTHOR; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_DESC; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_LAST_ENTRY; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_UPDATED; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_URL; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_PRIVATE_KEY; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_PUBLIC_KEY; + +class FeedFactoryImpl implements FeedFactory { + + private final CryptoComponent cryptoComponent; + private final AuthorFactory authorFactory; + private final BlogFactory blogFactory; + private final Clock clock; + + @Inject + FeedFactoryImpl(CryptoComponent cryptoComponent, + AuthorFactory authorFactory, BlogFactory blogFactory, Clock clock) { + this.cryptoComponent = cryptoComponent; + this.authorFactory = authorFactory; + this.blogFactory = blogFactory; + this.clock = clock; + } + + @Override + public Feed createFeed(String url, SyndFeed syndFeed) { + String title = syndFeed.getTitle(); + if (title == null) title = "RSS"; + title = StringUtils.truncateUtf8(title, MAX_AUTHOR_NAME_LENGTH); + + KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); + LocalAuthor localAuthor = authorFactory + .createLocalAuthor(title, + keyPair.getPublic().getEncoded(), + keyPair.getPrivate().getEncoded()); + Blog blog = blogFactory.createFeedBlog(localAuthor); + long added = clock.currentTimeMillis(); + + return new Feed(url, blog, localAuthor, added); + } + + @Override + public Feed createFeed(Feed feed, SyndFeed f, long lastEntryTime) { + long updated = clock.currentTimeMillis(); + return new Feed(feed.getUrl(), feed.getBlog(), feed.getLocalAuthor(), + f.getDescription(), f.getAuthor(), feed.getAdded(), updated, + lastEntryTime); + } + + @Override + public Feed createFeed(BdfDictionary d) throws FormatException { + String url = d.getString(KEY_FEED_URL); + + String blogTitle = d.getString(KEY_BLOG_TITLE); + byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY); + byte[] privateKey = d.getRaw(KEY_PRIVATE_KEY); + LocalAuthor localAuthor = authorFactory + .createLocalAuthor(blogTitle, publicKey, privateKey); + Blog blog = blogFactory.createFeedBlog(localAuthor); + + String desc = d.getOptionalString(KEY_FEED_DESC); + String author = d.getOptionalString(KEY_FEED_AUTHOR); + long added = d.getLong(KEY_FEED_ADDED, 0L); + long updated = d.getLong(KEY_FEED_UPDATED, 0L); + long lastEntryTime = d.getLong(KEY_FEED_LAST_ENTRY, 0L); + + return new Feed(url, blog, localAuthor, desc, author, added, + updated, lastEntryTime); + } + + @Override + public BdfDictionary feedToBdfDictionary(Feed feed) { + BdfDictionary d = BdfDictionary.of( + new BdfEntry(KEY_FEED_URL, feed.getUrl()), + new BdfEntry(KEY_BLOG_TITLE, feed.getLocalAuthor().getName()), + new BdfEntry(KEY_PUBLIC_KEY, + feed.getLocalAuthor().getPublicKey()), + new BdfEntry(KEY_PRIVATE_KEY, + feed.getLocalAuthor().getPrivateKey()), + new BdfEntry(KEY_FEED_ADDED, feed.getAdded()), + new BdfEntry(KEY_FEED_UPDATED, feed.getUpdated()), + new BdfEntry(KEY_FEED_LAST_ENTRY, feed.getLastEntryTime()) + ); + if (feed.getDescription() != null) + d.put(KEY_FEED_DESC, feed.getDescription()); + if (feed.getAuthor() != null) d.put(KEY_FEED_AUTHOR, feed.getAuthor()); + return d; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java index d92df5001d023cdd18777d355fffb687dc2ff90b..bb97bb0d48cf41956e00a4d89a84267e6792e7de 100644 --- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java @@ -18,7 +18,6 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventListener; -import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -31,6 +30,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.util.StringUtils; +import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPost; import org.briarproject.briar.api.blog.BlogPostFactory; @@ -39,8 +39,6 @@ import org.briarproject.briar.api.feed.FeedManager; import java.io.IOException; import java.io.InputStream; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; @@ -75,12 +73,12 @@ import static org.briarproject.briar.util.HtmlUtils.clean; @ThreadSafe @NotNullByDefault -class FeedManagerImpl implements FeedManager, Client, EventListener { +class FeedManagerImpl implements FeedManager, Client, EventListener, + BlogManager.RemoveBlogHook { private static final Logger LOG = Logger.getLogger(FeedManagerImpl.class.getName()); - private static final byte[] UNSPECIFIED_ADDRESS = new byte[4]; private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds private final ScheduledExecutorService scheduler; @@ -88,31 +86,33 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { private final DatabaseComponent db; private final ContactGroupFactory contactGroupFactory; private final ClientHelper clientHelper; - private final IdentityManager identityManager; private final BlogManager blogManager; private final BlogPostFactory blogPostFactory; + private final FeedFactory feedFactory; private final SocketFactory torSocketFactory; private final Clock clock; + private final Dns noDnsLookups; private final AtomicBoolean fetcherStarted = new AtomicBoolean(false); @Inject FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler, @IoExecutor Executor ioExecutor, DatabaseComponent db, ContactGroupFactory contactGroupFactory, ClientHelper clientHelper, - IdentityManager identityManager, BlogManager blogManager, - BlogPostFactory blogPostFactory, SocketFactory torSocketFactory, - Clock clock) { + BlogManager blogManager, BlogPostFactory blogPostFactory, + FeedFactory feedFactory, SocketFactory torSocketFactory, + Clock clock, Dns noDnsLookups) { this.scheduler = scheduler; this.ioExecutor = ioExecutor; this.db = db; this.contactGroupFactory = contactGroupFactory; this.clientHelper = clientHelper; - this.identityManager = identityManager; this.blogManager = blogManager; this.blogPostFactory = blogPostFactory; + this.feedFactory = feedFactory; this.torSocketFactory = torSocketFactory; this.clock = clock; + this.noDnsLookups = noDnsLookups; } @Override @@ -158,21 +158,21 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { } @Override - public void addFeed(String url, GroupId g) throws DbException, IOException { - LOG.info("Adding new RSS feed..."); - - // TODO check for existing feed? - // fetch feed to get its metadata - Feed feed = new Feed(url, g, clock.currentTimeMillis()); + public void addFeed(String url) throws DbException, IOException { + // fetch syndication feed to get its metadata + SyndFeed f; try { - feed = fetchFeed(feed, false); + f = fetchSyndFeed(url); } catch (FeedException e) { throw new IOException(e); } - // store feed + Feed feed = feedFactory.createFeed(url, f); + + // store feed and new blog Transaction txn = db.startTransaction(false); try { + blogManager.addBlog(txn, feed.getBlog()); List<Feed> feeds = getFeeds(txn); feeds.add(feed); storeFeeds(txn, feeds); @@ -181,10 +181,10 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { db.endTransaction(txn); } - // fetch feed again, post entries this time + // fetch feed again and post entries Feed updatedFeed; try { - updatedFeed = fetchFeed(feed, true); + updatedFeed = fetchFeed(feed); } catch (FeedException e) { throw new IOException(e); } @@ -203,27 +203,35 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { } @Override - public void removeFeed(String url) throws DbException { + public void removeFeed(Feed feed) throws DbException { LOG.info("Removing RSS feed..."); Transaction txn = db.startTransaction(false); try { - List<Feed> feeds = getFeeds(txn); - boolean found = false; - for (Feed feed : feeds) { - if (feed.getUrl().equals(url)) { - found = true; - feeds.remove(feed); - break; - } - } - if (!found) throw new DbException(); - storeFeeds(txn, feeds); + // this will call removingBlog() where the feed itself gets removed + blogManager.removeBlog(txn, feed.getBlog()); db.commitTransaction(txn); } finally { db.endTransaction(txn); } } + @Override + public void removingBlog(Transaction txn, Blog b) throws DbException { + if (!b.isRssFeed()) return; + + // delete blog's RSS feed if we have it + boolean found = false; + List<Feed> feeds = getFeeds(txn); + for (Feed f : feeds) { + if (f.getBlogId().equals(b.getId())) { + found = true; + feeds.remove(f); + break; + } + } + if (found) storeFeeds(txn, feeds); + } + @Override public List<Feed> getFeeds() throws DbException { List<Feed> feeds; @@ -246,7 +254,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { for (Object object : d.getList(KEY_FEEDS)) { if (!(object instanceof BdfDictionary)) throw new FormatException(); - feeds.add(Feed.from((BdfDictionary) object)); + feeds.add(feedFactory.createFeed((BdfDictionary) object)); } } catch (FormatException e) { throw new DbException(e); @@ -259,7 +267,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { BdfList feedList = new BdfList(); for (Feed feed : feeds) { - feedList.add(feed.toBdfDictionary()); + feedList.add(feedFactory.feedToBdfDictionary(feed)); } BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList)); try { @@ -300,7 +308,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { List<Feed> newFeeds = new ArrayList<Feed>(feeds.size()); for (Feed feed : feeds) { try { - newFeeds.add(fetchFeed(feed, true)); + newFeeds.add(fetchFeed(feed)); } catch (FeedException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -323,49 +331,52 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { LOG.info("Done updating RSS feeds"); } - private Feed fetchFeed(Feed feed, boolean post) - throws FeedException, IOException, DbException { - String title, description, author; - long updated = clock.currentTimeMillis(); - long lastEntryTime = feed.getLastEntryTime(); + private SyndFeed fetchSyndFeed(String url) + throws FeedException, IOException { + // fetch feed + SyndFeed f = getSyndFeed(getFeedInputStream(url)); + + if (f.getEntries().size() == 0) + throw new FeedException("Feed has no entries"); - SyndFeed f = getSyndFeed(getFeedInputStream(feed.getUrl())); - title = StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle(); + // clean title + String title = + StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle(); if (title != null) title = clean(title, STRIP_ALL); - description = StringUtils.isNullOrEmpty(f.getDescription()) ? null : - f.getDescription(); + f.setTitle(title); + + // clean description + String description = + StringUtils.isNullOrEmpty(f.getDescription()) ? null : + f.getDescription(); if (description != null) description = clean(description, STRIP_ALL); - author = + f.setDescription(description); + + // clean author + String author = StringUtils.isNullOrEmpty(f.getAuthor()) ? null : f.getAuthor(); if (author != null) author = clean(author, STRIP_ALL); + f.setAuthor(author); - if (f.getEntries().size() == 0) - throw new FeedException("Feed has no entries"); + return f; + } + + private Feed fetchFeed(Feed feed) + throws FeedException, IOException, DbException { + // fetch and clean feed + SyndFeed f = fetchSyndFeed(feed.getUrl()); // sort and add new entries - if (post) { - lastEntryTime = postFeedEntries(feed, f.getEntries()); - } - return new Feed(feed.getUrl(), feed.getBlogId(), title, description, - author, feed.getAdded(), updated, lastEntryTime); + long lastEntryTime = postFeedEntries(feed, f.getEntries()); + + return feedFactory.createFeed(feed, f, lastEntryTime); } private InputStream getFeedInputStream(String url) throws IOException { - // Don't make local DNS lookups - Dns noLookups = new Dns() { - @Override - public List<InetAddress> lookup(String hostname) - throws UnknownHostException { - InetAddress unspecified = - InetAddress.getByAddress(hostname, UNSPECIFIED_ADDRESS); - return Collections.singletonList(unspecified); - } - }; - // Build HTTP Client OkHttpClient client = new OkHttpClient.Builder() .socketFactory(torSocketFactory) - .dns(noLookups) + .dns(noDnsLookups) // Don't make local DNS lookups .connectTimeout(CONNECT_TIMEOUT, MILLISECONDS) .build(); @@ -422,9 +433,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { // build post body StringBuilder b = new StringBuilder(); - if (feed.getTitle() != null) { - b.append("<h3>").append(feed.getTitle()).append("</h3>"); - } + b.append("<h3>").append(feed.getTitle()).append("</h3>"); + if (!StringUtils.isNullOrEmpty(entry.getTitle())) { b.append("<h1>").append(entry.getTitle()).append("</h1>"); } @@ -461,9 +471,9 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { String body = getPostBody(b.toString()); try { // create and store post - LocalAuthor author = identityManager.getLocalAuthor(txn); + LocalAuthor localAuthor = feed.getLocalAuthor(); BlogPost post = blogPostFactory - .createBlogPost(groupId, time, null, author, body); + .createBlogPost(groupId, time, null, localAuthor, body); blogManager.addLocalPost(txn, post); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedModule.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedModule.java index d47a088b88000a74ee1974c7ab22c6d3e486c472..c4b567c189977888c2e2927fc956e9cb2c40f079 100644 --- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedModule.java @@ -2,6 +2,7 @@ package org.briarproject.briar.feed; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.feed.FeedManager; import javax.inject.Inject; @@ -21,11 +22,18 @@ public class FeedModule { @Provides @Singleton FeedManager provideFeedManager(FeedManagerImpl feedManager, - LifecycleManager lifecycleManager, EventBus eventBus) { + LifecycleManager lifecycleManager, EventBus eventBus, + BlogManager blogManager) { lifecycleManager.registerClient(feedManager); eventBus.addListener(feedManager); + blogManager.registerRemoveBlogHook(feedManager); return feedManager; } + @Provides + FeedFactory provideFeedFactory(FeedFactoryImpl feedFactory) { + return feedFactory; + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/NoDns.java b/briar-core/src/main/java/org/briarproject/briar/feed/NoDns.java new file mode 100644 index 0000000000000000000000000000000000000000..5f5cc8c44b3650a7c169fd41ea04e1f242124631 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/feed/NoDns.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.feed; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import okhttp3.Dns; + +class NoDns implements Dns { + + private static final byte[] UNSPECIFIED_ADDRESS = new byte[4]; + + @Inject + public NoDns() { + } + + @Override + public List<InetAddress> lookup(String hostname) + throws UnknownHostException { + InetAddress unspecified = + InetAddress.getByAddress(hostname, UNSPECIFIED_ADDRESS); + return Collections.singletonList(unspecified); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java index 6f0df8e473b3db7b1e1361850adb2cff997523a8..f9b378ef7878628b4c29f323b54437e393d47f8d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java @@ -39,14 +39,20 @@ class BlogSharingValidator extends SharingValidator { @Override protected GroupId validateDescriptor(BdfList descriptor) throws FormatException { - checkSize(descriptor, 2); + checkSize(descriptor, 3); String name = descriptor.getString(0); checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); byte[] publicKey = descriptor.getRaw(1); checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH); + boolean rssFeed = descriptor.getBoolean(2); Author author = authorFactory.createAuthor(name, publicKey); - Blog blog = blogFactory.createBlog(author); + Blog blog; + if (rssFeed) { + blog = blogFactory.createFeedBlog(author); + } else { + blog = blogFactory.createBlog(author); + } return blog.getId(); } diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java index e2b5e8ef5206d162cc029c3cd945362f894eaa96..4ff1bb75c4826d35c093fc7a8815f9acaa32975f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java @@ -297,7 +297,7 @@ public class BlogManagerImplTest extends BriarTestCase { final LocalAuthor localAuthor = new LocalAuthor(authorId, "Author", publicKey, privateKey, created); - return new Blog(group, localAuthor); + return new Blog(group, localAuthor, false); } private BdfDictionary authorToBdfDictionary(Author a) { diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java index 643fe482c3b939047029731aecce9773324221c7..165c1fab52d764f43b4cd2cd8858b0ac082c2400 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java @@ -1,5 +1,7 @@ package org.briarproject.briar.blog; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.briar.api.blog.Blog; @@ -32,7 +34,7 @@ public class BlogManagerIntegrationTest extends BriarIntegrationTest<BriarIntegrationTestComponent> { private BlogManager blogManager0, blogManager1; - private Blog blog0, blog1; + private Blog blog0, blog1, rssBlog; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -50,6 +52,12 @@ public class BlogManagerIntegrationTest blog0 = blogFactory.createBlog(author0); blog1 = blogFactory.createBlog(author1); + + rssBlog = blogFactory.createFeedBlog(author0); + Transaction txn = db0.startTransaction(false); + blogManager0.addBlog(txn, rssBlog); + db0.commitTransaction(txn); + db0.endTransaction(txn); } @Override @@ -393,4 +401,63 @@ public class BlogManagerIntegrationTest assertEquals(2, headers0.size()); } + @Test + public void testFeedPost() throws Exception { + assertTrue(rssBlog.isRssFeed()); + + // add a feed post to rssBlog + final String body = getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(rssBlog.getId(), clock.currentTimeMillis(), + null, author0, body); + blogManager0.addLocalPost(p); + + // make sure it got saved as an RSS feed post + Collection<BlogPostHeader> headers = + blogManager0.getPostHeaders(rssBlog.getId()); + assertEquals(1, headers.size()); + BlogPostHeader header = headers.iterator().next(); + assertEquals(POST, header.getType()); + assertEquals(Author.Status.NONE, header.getAuthorStatus()); + assertTrue(header.isRssFeed()); + } + + @Test + public void testFeedReblog() throws Exception { + // add a feed post to rssBlog + final String body = getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(rssBlog.getId(), clock.currentTimeMillis(), + null, author0, body); + blogManager0.addLocalPost(p); + + // reblog feed post to own blog + Collection<BlogPostHeader> headers = + blogManager0.getPostHeaders(rssBlog.getId()); + assertEquals(1, headers.size()); + BlogPostHeader header = headers.iterator().next(); + blogManager0.addLocalComment(author0, blog0.getId(), null, header); + + // make sure it got saved as an RSS feed post + headers = blogManager0.getPostHeaders(blog0.getId()); + assertEquals(1, headers.size()); + BlogCommentHeader commentHeader = + (BlogCommentHeader) headers.iterator().next(); + assertEquals(COMMENT, commentHeader.getType()); + assertTrue(commentHeader.getParent().isRssFeed()); + + // reblog reblogged post again to own blog + blogManager0 + .addLocalComment(author0, blog0.getId(), null, commentHeader); + + // make sure it got saved as an RSS feed post + headers = blogManager0.getPostHeaders(blog0.getId()); + assertEquals(2, headers.size()); + for (BlogPostHeader h: headers) { + assertTrue(h instanceof BlogCommentHeader); + assertEquals(COMMENT, h.getType()); + assertTrue(((BlogCommentHeader) h).getRootPost().isRssFeed()); + } + } + } diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java index 1ac77a550e8d3737eadd84e447e9981abb0d443f..d3731d19de9c9fcea112144ab37589e782081793 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java @@ -79,7 +79,7 @@ public class BlogPostValidatorTest extends BriarTestCase { new BdfEntry(KEY_AUTHOR_NAME, author.getName()), new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey()) ); - blog = new Blog(group, author); + blog = new Blog(group, author, false); MessageId messageId = new MessageId(TestUtils.getRandomId()); long timestamp = System.currentTimeMillis(); diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2fc24aae58f3f082491e5ca6011ca7ca05d9100a --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java @@ -0,0 +1,127 @@ +package org.briarproject.briar.feed; + +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoModule; +import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.lifecycle.LifecycleModule; +import org.briarproject.bramble.sync.SyncModule; +import org.briarproject.bramble.system.SystemModule; +import org.briarproject.bramble.test.TestDatabaseModule; +import org.briarproject.bramble.test.TestUtils; +import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.briar.api.blog.Blog; +import org.briarproject.briar.api.blog.BlogManager; +import org.briarproject.briar.api.blog.BlogPostHeader; +import org.briarproject.briar.api.feed.Feed; +import org.briarproject.briar.api.feed.FeedManager; +import org.briarproject.briar.blog.BlogModule; +import org.briarproject.briar.test.BriarTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class FeedManagerIntegrationTest extends BriarTestCase { + + private LifecycleManager lifecycleManager; + private FeedManager feedManager; + private BlogManager blogManager; + private final File testDir = TestUtils.getTestDirectory(); + private final File testFile = new File(testDir, "feedTest"); + + @Before + public void setUp() throws Exception { + assertTrue(testDir.mkdirs()); + FeedManagerIntegrationTestComponent component = + DaggerFeedManagerIntegrationTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(testFile)) + .build(); + component.inject(this); + injectEagerSingletons(component); + + lifecycleManager = component.getLifecycleManager(); + lifecycleManager.startServices("feedTest"); + lifecycleManager.waitForStartup(); + + feedManager = component.getFeedManager(); + blogManager = component.getBlogManager(); + } + + @Test + public void testFeedImportAndRemoval() throws Exception { + // initially, there's only the one personal blog + Collection<Blog> blogs = blogManager.getBlogs(); + assertEquals(1, blogs.size()); + Blog personalBlog = blogs.iterator().next(); + + // add feed into a dedicated blog + String url = "https://www.schneier.com/blog/atom.xml"; + feedManager.addFeed(url); + + // then there's the feed's blog now + blogs = blogManager.getBlogs(); + assertEquals(2, blogs.size()); + Blog feedBlog = null; + for (Blog blog : blogs) { + if (!blog.equals(personalBlog)) feedBlog = blog; + } + assertNotNull(feedBlog); + + // check the feed got saved as expected + Collection<Feed> feeds = feedManager.getFeeds(); + assertEquals(1, feeds.size()); + Feed feed = feeds.iterator().next(); + assertTrue(feed.getLastEntryTime() > 0); + assertTrue(feed.getAdded() > 0); + assertTrue(feed.getUpdated() > 0); + assertEquals(url, feed.getUrl()); + assertEquals(feedBlog, feed.getBlog()); + assertEquals("Schneier on Security", feed.getTitle()); + assertEquals("A blog covering security and security technology.", + feed.getDescription()); + assertEquals(feed.getTitle(), feed.getBlog().getName()); + assertEquals(feed.getTitle(), feed.getLocalAuthor().getName()); + + // check the feed entries have been added to the blog as expected + Collection<BlogPostHeader> headers = + blogManager.getPostHeaders(feedBlog.getId()); + for (BlogPostHeader header : headers) { + assertTrue(header.isRssFeed()); + } + + // now let's remove the feed's blog again + blogManager.removeBlog(feedBlog); + blogs = blogManager.getBlogs(); + assertEquals(1, blogs.size()); + assertEquals(personalBlog, blogs.iterator().next()); + assertEquals(0, feedManager.getFeeds().size()); + } + + @After + public void tearDown() throws Exception { + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + TestUtils.deleteTestDirectory(testDir); + } + + protected void injectEagerSingletons( + FeedManagerIntegrationTestComponent component) { + component.inject(new FeedModule.EagerSingletons()); + component.inject(new BlogModule.EagerSingletons()); + component.inject(new ContactModule.EagerSingletons()); + component.inject(new CryptoModule.EagerSingletons()); + component.inject(new IdentityModule.EagerSingletons()); + component.inject(new LifecycleModule.EagerSingletons()); + component.inject(new SyncModule.EagerSingletons()); + component.inject(new SystemModule.EagerSingletons()); + component.inject(new TransportModule.EagerSingletons()); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..ce5ec5a00dfecbc9d2b5c8b6f0af92abb5d776e1 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java @@ -0,0 +1,79 @@ +package org.briarproject.briar.feed; + +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.client.ClientModule; +import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoModule; +import org.briarproject.bramble.data.DataModule; +import org.briarproject.bramble.db.DatabaseModule; +import org.briarproject.bramble.event.EventModule; +import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.lifecycle.LifecycleModule; +import org.briarproject.bramble.sync.SyncModule; +import org.briarproject.bramble.system.SystemModule; +import org.briarproject.bramble.test.TestDatabaseModule; +import org.briarproject.bramble.test.TestPluginConfigModule; +import org.briarproject.bramble.test.TestSeedProviderModule; +import org.briarproject.bramble.test.TestSocksModule; +import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.briar.api.blog.BlogManager; +import org.briarproject.briar.api.feed.FeedManager; +import org.briarproject.briar.blog.BlogModule; +import org.briarproject.briar.client.BriarClientModule; +import org.briarproject.briar.test.TestDnsModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestDatabaseModule.class, + TestPluginConfigModule.class, + TestSeedProviderModule.class, + TestSocksModule.class, + TestDnsModule.class, + LifecycleModule.class, + BriarClientModule.class, + ClientModule.class, + ContactModule.class, + CryptoModule.class, + BlogModule.class, + FeedModule.class, + DataModule.class, + DatabaseModule.class, + EventModule.class, + IdentityModule.class, + SyncModule.class, + SystemModule.class, + TransportModule.class +}) +interface FeedManagerIntegrationTestComponent { + + void inject(FeedManagerIntegrationTest testCase); + + void inject(FeedModule.EagerSingletons init); + + void inject(BlogModule.EagerSingletons init); + + void inject(ContactModule.EagerSingletons init); + + void inject(CryptoModule.EagerSingletons init); + + void inject(IdentityModule.EagerSingletons init); + + void inject(LifecycleModule.EagerSingletons init); + + void inject(SyncModule.EagerSingletons init); + + void inject(SystemModule.EagerSingletons init); + + void inject(TransportModule.EagerSingletons init); + + LifecycleManager getLifecycleManager(); + + FeedManager getFeedManager(); + + BlogManager getBlogManager(); + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java index c67360d648b933c8a543e45ea640893f8621a9db..7c4c96ab267cb9f52f769abc2771221b911b75f1 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java @@ -23,8 +23,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { private final byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); private final Author author = new Author(authorId, authorName, publicKey); - private final Blog blog = new Blog(group, author); - private final BdfList descriptor = BdfList.of(authorName, publicKey); + private final Blog blog = new Blog(group, author, false); + private final BdfList descriptor = BdfList.of(authorName, publicKey, false); private final String content = TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH); @@ -64,7 +64,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsNullBlogName() throws Exception { - BdfList invalidDescriptor = BdfList.of(null, publicKey); + BdfList invalidDescriptor = BdfList.of(null, publicKey, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -72,7 +72,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsNonStringBlogName() throws Exception { - BdfList invalidDescriptor = BdfList.of(123, publicKey); + BdfList invalidDescriptor = BdfList.of(123, publicKey, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -80,7 +80,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsTooShortBlogName() throws Exception { - BdfList invalidDescriptor = BdfList.of("", publicKey); + BdfList invalidDescriptor = BdfList.of("", publicKey, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -89,7 +89,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test public void testAcceptsMinLengthBlogName() throws Exception { String shortBlogName = TestUtils.getRandomString(1); - BdfList validDescriptor = BdfList.of(shortBlogName, publicKey); + BdfList validDescriptor = BdfList.of(shortBlogName, publicKey, false); expectCreateBlog(shortBlogName, publicKey); expectEncodeMetadata(INVITE); BdfMessageContext messageContext = v.validateMessage(message, group, @@ -102,7 +102,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { public void testRejectsTooLongBlogName() throws Exception { String invalidBlogName = TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1); - BdfList invalidDescriptor = BdfList.of(invalidBlogName, publicKey); + BdfList invalidDescriptor = + BdfList.of(invalidBlogName, publicKey, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -110,7 +111,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsNullPublicKey() throws Exception { - BdfList invalidDescriptor = BdfList.of(authorName, null); + BdfList invalidDescriptor = BdfList.of(authorName, null, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -118,7 +119,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsNonRawPublicKey() throws Exception { - BdfList invalidDescriptor = BdfList.of(authorName, 123); + BdfList invalidDescriptor = BdfList.of(authorName, 123, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -127,7 +128,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test(expected = FormatException.class) public void testRejectsTooLongPublicKey() throws Exception { byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1); - BdfList invalidDescriptor = BdfList.of(authorName, invalidKey); + BdfList invalidDescriptor = BdfList.of(authorName, invalidKey, false); v.validateMessage(message, group, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, null)); @@ -136,7 +137,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest { @Test public void testAcceptsMinLengthPublicKey() throws Exception { byte[] key = TestUtils.getRandomBytes(1); - BdfList validDescriptor = BdfList.of(authorName, key); + BdfList validDescriptor = BdfList.of(authorName, key, false); expectCreateBlog(authorName, key); expectEncodeMetadata(INVITE); diff --git a/briar-core/src/test/java/org/briarproject/briar/test/TestDnsModule.java b/briar-core/src/test/java/org/briarproject/briar/test/TestDnsModule.java new file mode 100644 index 0000000000000000000000000000000000000000..13323b346fb57a10f6fe6bd8f76906a96c857dad --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/test/TestDnsModule.java @@ -0,0 +1,15 @@ +package org.briarproject.briar.test; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Dns; + +@Module +public class TestDnsModule { + + @Provides + Dns provideDns() { + return Dns.SYSTEM; + } + +}