Merge branch '892-separate-rss-blog' into 'master'

Separate RSS posts from personal blog posts

Closes #892

See merge request !520
This commit is contained in:
akwizgran
2017-04-13 10:15:00 +00:00
41 changed files with 861 additions and 223 deletions

View File

@@ -13,7 +13,9 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class Author { 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 AuthorId id;
private final String name; private final String name;

View File

@@ -68,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@NotNullByDefault @NotNullByDefault
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 29; private static final int SCHEMA_VERSION = 30;
private static final int MIN_SCHEMA_VERSION = 29; private static final int MIN_SCHEMA_VERSION = 30;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"

View File

@@ -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();
}
}

View File

@@ -48,6 +48,10 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
return body; return body;
} }
public boolean isRssFeed() {
return header.isRssFeed();
}
public boolean isRead() { public boolean isRead() {
return read; return read;
} }

View File

@@ -108,7 +108,8 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setAuthor(a); author.setAuthor(a);
author.setAuthorStatus(post.getAuthorStatus()); author.setAuthorStatus(post.getAuthorStatus());
author.setDate(post.getTimestamp()); author.setDate(post.getTimestamp());
author.setPersona(AuthorView.NORMAL); author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624 // TODO make author clickable more often #624
if (item.getHeader().getType() == POST) { if (item.getHeader().getType() == POST) {
author.setBlogLink(post.getGroupId()); author.setBlogLink(post.getGroupId());
@@ -168,7 +169,9 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setVisibility(VISIBLE); reblogger.setVisibility(VISIBLE);
reblogger.setPersona(AuthorView.REBLOGGER); reblogger.setPersona(AuthorView.REBLOGGER);
author.setPersona(AuthorView.COMMENTER); author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
AuthorView.RSS_FEED_REBLOGGED :
AuthorView.COMMENTER);
// comments // comments
for (BlogCommentHeader c : item.getComments()) { for (BlogCommentHeader c : item.getComments()) {

View File

@@ -179,7 +179,6 @@ public class FeedFragment extends BaseFragment implements
case R.id.action_rss_feeds_import: case R.id.action_rss_feeds_import:
Intent i2 = Intent i2 =
new Intent(getActivity(), RssFeedImportActivity.class); new Intent(getActivity(), RssFeedImportActivity.class);
i2.putExtra(GROUP_ID, personalBlog.getId().getBytes());
startActivity(i2); startActivity(i2);
return true; return true;
case R.id.action_rss_feeds_manage: case R.id.action_rss_feeds_manage:

View File

@@ -6,7 +6,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -39,12 +39,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
if (item == null) return; if (item == null) return;
// Feed Title // Feed Title
if (item.getTitle() != null) { ui.title.setText(item.getTitle());
ui.title.setText(item.getTitle());
ui.title.setVisibility(VISIBLE);
} else {
ui.title.setVisibility(GONE);
}
// Delete Button // Delete Button
ui.delete.setOnClickListener(new OnClickListener() { ui.delete.setOnClickListener(new OnClickListener() {
@@ -75,6 +70,14 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
} else { } else {
ui.description.setVisibility(GONE); 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 @Override
@@ -99,8 +102,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
} }
static class FeedViewHolder extends RecyclerView.ViewHolder { static class FeedViewHolder extends RecyclerView.ViewHolder {
private final View layout;
private final TextView title; private final TextView title;
private final ImageView delete; private final ImageButton delete;
private final TextView imported; private final TextView imported;
private final TextView updated; private final TextView updated;
private final TextView author; private final TextView author;
@@ -110,8 +114,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
private FeedViewHolder(View v) { private FeedViewHolder(View v) {
super(v); super(v);
layout = v;
title = (TextView) v.findViewById(R.id.titleView); 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); imported = (TextView) v.findViewById(R.id.importedView);
updated = (TextView) v.findViewById(R.id.updatedView); updated = (TextView) v.findViewById(R.id.updatedView);
author = (TextView) v.findViewById(R.id.authorView); author = (TextView) v.findViewById(R.id.authorView);
@@ -121,6 +126,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
} }
interface RssFeedListener { interface RssFeedListener {
void onFeedClick(Feed feed);
void onDeleteClick(Feed feed); void onDeleteClick(Feed feed);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.blog; package org.briarproject.briar.android.blog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
@@ -15,7 +14,6 @@ import android.widget.ProgressBar;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
@@ -44,9 +42,6 @@ public class RssFeedImportActivity extends BriarActivity {
@IoExecutor @IoExecutor
Executor ioExecutor; Executor ioExecutor;
// Fields that are accessed from background threads must be volatile
private volatile GroupId groupId = null;
@Inject @Inject
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
volatile FeedManager feedManager; volatile FeedManager feedManager;
@@ -55,12 +50,6 @@ public class RssFeedImportActivity extends BriarActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.activity_rss_feed_import);
urlInput = (EditText) findViewById(R.id.urlInput); urlInput = (EditText) findViewById(R.id.urlInput);
@@ -128,7 +117,7 @@ public class RssFeedImportActivity extends BriarActivity {
@Override @Override
public void run() { public void run() {
try { try {
feedManager.addFeed(url, groupId); feedManager.addFeed(url);
feedImported(); feedImported();
} catch (DbException | IOException e) { } catch (DbException | IOException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -1,15 +1,16 @@
package org.briarproject.briar.android.blog; package org.briarproject.briar.android.blog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
@@ -23,6 +24,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG; import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -34,7 +36,6 @@ public class RssFeedManageActivity extends BriarActivity
private BriarRecyclerView list; private BriarRecyclerView list;
private RssFeedAdapter adapter; private RssFeedAdapter adapter;
private GroupId groupId;
@Inject @Inject
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@@ -44,12 +45,6 @@ public class RssFeedManageActivity extends BriarActivity
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.activity_rss_feed_manage);
adapter = new RssFeedAdapter(this, this); adapter = new RssFeedAdapter(this, this);
@@ -87,7 +82,6 @@ public class RssFeedManageActivity extends BriarActivity
return true; return true;
case R.id.action_rss_feeds_import: case R.id.action_rss_feeds_import:
Intent i = new Intent(this, RssFeedImportActivity.class); Intent i = new Intent(this, RssFeedImportActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i); startActivity(i);
return true; return true;
default: default:
@@ -100,21 +94,32 @@ public class RssFeedManageActivity extends BriarActivity
component.inject(this); 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 @Override
public void onDeleteClick(final Feed feed) { public void onDeleteClick(final Feed feed) {
runOnDbThread(new Runnable() { DialogInterface.OnClickListener okListener =
@Override new DialogInterface.OnClickListener() {
public void run() { @Override
try { public void onClick(DialogInterface dialog, int which) {
feedManager.removeFeed(feed.getUrl()); deleteFeed(feed);
onFeedDeleted(feed); }
} catch (DbException e) { };
if (LOG.isLoggable(WARNING)) AlertDialog.Builder builder = new AlertDialog.Builder(this,
LOG.log(WARNING, e.toString(), e); R.style.BriarDialogTheme);
onDeleteError(); 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() { 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() { private void onLoadError() {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override

View File

@@ -30,6 +30,7 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.graphics.Typeface.BOLD; import static android.graphics.Typeface.BOLD;
import static android.util.TypedValue.COMPLEX_UNIT_PX; 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.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; 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 REBLOGGER = 1;
public static final int COMMENTER = 2; public static final int COMMENTER = 2;
public static final int LIST = 3; 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 CircleImageView avatar;
private final ImageView avatarIcon; private final ImageView avatarIcon;
@@ -83,7 +86,13 @@ public class AuthorView extends RelativeLayout {
} }
public void setAuthorStatus(Status status) { public void setAuthorStatus(Status status) {
trustIndicator.setTrustLevel(status); if (status != NONE) {
trustIndicator.setTrustLevel(status);
trustIndicator.setVisibility(VISIBLE);
} else {
trustIndicator.setVisibility(GONE);
}
if (status == OURSELVES) { if (status == OURSELVES) {
authorName.setTypeface(authorNameTypeface, BOLD); authorName.setTypeface(authorNameTypeface, BOLD);
} else { } else {
@@ -124,10 +133,17 @@ public class AuthorView extends RelativeLayout {
setOnClickListener(null); 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) { public void setPersona(int persona) {
switch (persona) { switch (persona) {
case NORMAL: case NORMAL:
avatarIcon.setVisibility(VISIBLE); avatarIcon.setVisibility(INVISIBLE);
date.setVisibility(VISIBLE); date.setVisibility(VISIBLE);
setAvatarSize(R.dimen.blogs_avatar_normal_size); setAvatarSize(R.dimen.blogs_avatar_normal_size);
setTextSize(authorName, R.dimen.text_size_small); setTextSize(authorName, R.dimen.text_size_small);
@@ -158,6 +174,24 @@ public class AuthorView extends RelativeLayout {
setCenterVertical(authorName, true); setCenterVertical(authorName, true);
setCenterVertical(trustIndicator, true); setCenterVertical(trustIndicator, true);
break; 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;
} }
} }

View File

@@ -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>

View File

@@ -19,7 +19,7 @@
android:textSize="@dimen/text_size_medium" android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a RSS Feed"/> tools:text="This is a name of a RSS Feed"/>
<ImageView <ImageButton
android:id="@+id/deleteButton" android:id="@+id/deleteButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -12,6 +12,8 @@
<enum name="reblogger" value="1"/> <enum name="reblogger" value="1"/>
<enum name="commenter" value="2"/> <enum name="commenter" value="2"/>
<enum name="list" value="3"/> <enum name="list" value="3"/>
<enum name="rss_feed" value="4"/>
<enum name="rss_feed_reblogged" value="5"/>
</attr> </attr>
</declare-styleable> </declare-styleable>

View File

@@ -304,6 +304,9 @@
<string name="blogs_rss_feeds_manage_imported">Imported:</string> <string name="blogs_rss_feeds_manage_imported">Imported:</string>
<string name="blogs_rss_feeds_manage_author">Author:</string> <string name="blogs_rss_feeds_manage_author">Author:</string>
<string name="blogs_rss_feeds_manage_updated">Last Updated:</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_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_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> <string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string>

View File

@@ -13,16 +13,22 @@ import javax.annotation.concurrent.Immutable;
public class Blog extends BaseGroup implements Shareable { public class Blog extends BaseGroup implements Shareable {
private final Author author; private final Author author;
private final boolean rssFeed;
public Blog(Group group, Author author) { public Blog(Group group, Author author, boolean rssFeed) {
super(group); super(group);
this.author = author; this.author = author;
this.rssFeed = rssFeed;
} }
public Author getAuthor() { public Author getAuthor() {
return author; return author;
} }
public boolean isRssFeed() {
return rssFeed;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof Blog && super.equals(o); return o instanceof Blog && super.equals(o);

View File

@@ -26,7 +26,7 @@ public class BlogCommentHeader extends BlogPostHeader {
Status authorStatus, boolean read) { Status authorStatus, boolean read) {
super(type, groupId, id, parent.getId(), timestamp, super(type, groupId, id, parent.getId(), timestamp,
timeReceived, author, authorStatus, read); timeReceived, author, authorStatus, false, read);
if (type != COMMENT && type != WRAPPED_COMMENT) if (type != COMMENT && type != WRAPPED_COMMENT)
throw new IllegalArgumentException("Incompatible Message Type"); throw new IllegalArgumentException("Incompatible Message Type");
@@ -43,4 +43,11 @@ public class BlogCommentHeader extends BlogPostHeader {
public BlogPostHeader getParent() { public BlogPostHeader getParent() {
return parent; return parent;
} }
public BlogPostHeader getRootPost() {
if (parent instanceof BlogCommentHeader)
return ((BlogCommentHeader) parent).getRootPost();
return parent;
}
} }

View File

@@ -28,6 +28,7 @@ public interface BlogConstants {
String KEY_AUTHOR_NAME = "name"; String KEY_AUTHOR_NAME = "name";
String KEY_PUBLIC_KEY = "publicKey"; String KEY_PUBLIC_KEY = "publicKey";
String KEY_AUTHOR = "author"; String KEY_AUTHOR = "author";
String KEY_RSS_FEED = "rssFeed";
String KEY_READ = "read"; String KEY_READ = "read";
String KEY_COMMENT = "comment"; String KEY_COMMENT = "comment";
String KEY_ORIGINAL_MSG_ID = "originalMessageId"; String KEY_ORIGINAL_MSG_ID = "originalMessageId";

View File

@@ -13,6 +13,11 @@ public interface BlogFactory {
*/ */
Blog createBlog(Author author); Blog createBlog(Author author);
/**
* Creates a RSS feed blog for a given author.
*/
Blog createFeedBlog(Author author);
/** /**
* Parses a blog with the given Group * Parses a blog with the given Group
*/ */

View File

@@ -41,6 +41,11 @@ public interface BlogManager {
*/ */
void removeBlog(Blog b) throws DbException; 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. * Stores a local blog post.
*/ */

View File

@@ -17,21 +17,23 @@ public class BlogPostHeader extends PostHeader {
private final MessageType type; private final MessageType type;
private final GroupId groupId; private final GroupId groupId;
private final long timeReceived; private final long timeReceived;
private final boolean rssFeed;
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id, public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
@Nullable MessageId parentId, long timestamp, long timeReceived, @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); super(id, parentId, timestamp, author, authorStatus, read);
this.type = type; this.type = type;
this.groupId = groupId; this.groupId = groupId;
this.timeReceived = timeReceived; this.timeReceived = timeReceived;
this.rssFeed = rssFeed;
} }
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id, public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
long timestamp, long timeReceived, Author author, long timestamp, long timeReceived, Author author,
Status authorStatus, boolean read) { Status authorStatus, boolean rssFeed, boolean read) {
this(type, groupId, id, null, timestamp, timeReceived, author, this(type, groupId, id, null, timestamp, timeReceived, author,
authorStatus, read); authorStatus, rssFeed, read);
} }
public MessageType getType() { public MessageType getType() {
@@ -45,4 +47,9 @@ public class BlogPostHeader extends PostHeader {
public long getTimeReceived() { public long getTimeReceived() {
return timeReceived; return timeReceived;
} }
public boolean isRssFeed() {
return rssFeed;
}
} }

View File

@@ -1,40 +1,31 @@
package org.briarproject.briar.api.feed; package org.briarproject.briar.api.feed;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.blog.Blog;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; 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 @Immutable
@NotNullByDefault @NotNullByDefault
public class Feed { public class Feed {
private final String url; private final String url;
private final GroupId blogId; private final Blog blog;
private final LocalAuthor localAuthor;
@Nullable @Nullable
private final String title, description, author; private final String description, author;
private final long added, updated, lastEntryTime; private final long added, updated, lastEntryTime;
public Feed(String url, GroupId blogId, @Nullable String title, public Feed(String url, Blog blog, LocalAuthor localAuthor,
@Nullable String description, @Nullable String author, @Nullable String description, @Nullable String author, long added,
long added, long updated, long lastEntryTime) { long updated, long lastEntryTime) {
this.url = url; this.url = url;
this.blogId = blogId; this.blog = blog;
this.title = title; this.localAuthor = localAuthor;
this.description = description; this.description = description;
this.author = author; this.author = author;
this.added = added; this.added = added;
@@ -42,13 +33,13 @@ public class Feed {
this.lastEntryTime = lastEntryTime; 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) { @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) { public Feed(String url, Blog blog, LocalAuthor localAuthor, long added) {
this(url, blogId, null, null, null, added, 0L, 0L); this(url, blog, localAuthor, null, null, added, 0L, 0L);
} }
public String getUrl() { public String getUrl() {
@@ -56,39 +47,19 @@ public class Feed {
} }
public GroupId getBlogId() { public GroupId getBlogId() {
return blogId; return blog.getId();
} }
public BdfDictionary toBdfDictionary() { public Blog getBlog() {
BdfDictionary d = BdfDictionary.of( return blog;
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 static Feed from(BdfDictionary d) throws FormatException { public LocalAuthor getLocalAuthor() {
String url = d.getString(KEY_FEED_URL); return localAuthor;
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);
} }
@Nullable
public String getTitle() { public String getTitle() {
return title; return blog.getName();
} }
@Nullable @Nullable
@@ -118,20 +89,9 @@ public class Feed {
if (this == o) return true; if (this == o) return true;
if (o instanceof Feed) { if (o instanceof Feed) {
Feed f = (Feed) o; Feed f = (Feed) o;
return url.equals(f.url) && blogId.equals(f.getBlogId()) && return blog.equals(f.blog);
equalsWithNull(title, f.getTitle()) &&
equalsWithNull(description, f.getDescription()) &&
equalsWithNull(author, f.getAuthor()) &&
added == f.getAdded() &&
updated == f.getUpdated() &&
lastEntryTime == f.getLastEntryTime();
} }
return false; 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);
}
} }

View File

@@ -18,8 +18,9 @@ public interface FeedConstants {
// group metadata keys // group metadata keys
String KEY_FEEDS = "feeds"; String KEY_FEEDS = "feeds";
String KEY_FEED_URL = "feedURL"; String KEY_FEED_URL = "feedURL";
String KEY_BLOG_GROUP_ID = "blogGroupId"; String KEY_BLOG_TITLE = "blogTitle";
String KEY_FEED_TITLE = "feedTitle"; String KEY_PUBLIC_KEY = "publicKey";
String KEY_PRIVATE_KEY = "privateKey";
String KEY_FEED_DESC = "feedDesc"; String KEY_FEED_DESC = "feedDesc";
String KEY_FEED_AUTHOR = "feedAuthor"; String KEY_FEED_AUTHOR = "feedAuthor";
String KEY_FEED_ADDED = "feedAdded"; String KEY_FEED_ADDED = "feedAdded";

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.api.feed;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.GroupId;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@@ -17,14 +16,14 @@ public interface FeedManager {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed"); 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. * Removes an RSS feed.
*/ */
void removeFeed(String url) throws DbException; void removeFeed(Feed feed) throws DbException;
/** /**
* Returns a list of all added RSS feeds * Returns a list of all added RSS feeds

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar;
import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.blog.BlogModule;
import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.client.BriarClientModule;
import org.briarproject.briar.feed.DnsModule;
import org.briarproject.briar.feed.FeedModule; import org.briarproject.briar.feed.FeedModule;
import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.forum.ForumModule;
import org.briarproject.briar.introduction.IntroductionModule; import org.briarproject.briar.introduction.IntroductionModule;
@@ -16,6 +17,7 @@ import dagger.Module;
BlogModule.class, BlogModule.class,
BriarClientModule.class, BriarClientModule.class,
FeedModule.class, FeedModule.class,
DnsModule.class,
ForumModule.class, ForumModule.class,
GroupInvitationModule.class, GroupInvitationModule.class,
IntroductionModule.class, IntroductionModule.class,

View File

@@ -14,6 +14,9 @@ import org.briarproject.briar.api.blog.BlogFactory;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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 @Immutable
@NotNullByDefault @NotNullByDefault
class BlogFactoryImpl implements BlogFactory { class BlogFactoryImpl implements BlogFactory {
@@ -33,28 +36,46 @@ class BlogFactoryImpl implements BlogFactory {
@Override @Override
public Blog createBlog(Author a) { 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 { try {
BdfList blog = BdfList.of( BdfList blog = BdfList.of(
a.getName(), a.getName(),
a.getPublicKey() a.getPublicKey(),
rssFeed
); );
byte[] descriptor = clientHelper.toByteArray(blog); byte[] descriptor = clientHelper.toByteArray(blog);
Group g = groupFactory Group g = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor); .createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
return new Blog(g, a); return new Blog(g, a, rssFeed);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Override @Override
public Blog parseBlog(Group g) throws FormatException { public Blog parseBlog(Group group) throws FormatException {
byte[] descriptor = g.getDescriptor(); byte[] descriptor = group.getDescriptor();
// Author Name, Public Key // Author Name, Public Key
BdfList blog = clientHelper.toList(descriptor); BdfList blog = clientHelper.toList(descriptor);
Author a = String name = blog.getString(0);
authorFactory.createAuthor(blog.getString(0), blog.getRaw(1)); if (name.length() > MAX_AUTHOR_NAME_LENGTH)
return new Blog(g, a); 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);
} }
} }

View File

@@ -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_PARENT_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY; 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_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_TIMESTAMP;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; 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) private void removeBlog(Transaction txn, Blog b, boolean forced)
throws DbException { throws DbException {
if (!forced && !canBeRemoved(txn, b.getId())) if (!forced && !canBeRemoved(txn, b.getId()))
@@ -248,15 +254,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public void addLocalPost(Transaction txn, BlogPost p) throws DbException { public void addLocalPost(Transaction txn, BlogPost p) throws DbException {
try { try {
GroupId groupId = p.getMessage().getGroupId();
Blog b = getBlog(txn, groupId);
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_TYPE, POST.getInt()); meta.put(KEY_TYPE, POST.getInt());
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor())); meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
meta.put(KEY_READ, true); meta.put(KEY_READ, true);
meta.put(KEY_RSS_FEED, b.isRssFeed());
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true); clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
// broadcast event about new post // broadcast event about new post
GroupId groupId = p.getMessage().getGroupId();
MessageId postId = p.getMessage().getId(); MessageId postId = p.getMessage().getId();
BlogPostHeader h = BlogPostHeader h =
getPostHeaderFromMetadata(txn, groupId, postId, meta); getPostHeaderFromMetadata(txn, groupId, postId, meta);
@@ -345,6 +354,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
wMessage = blogPostFactory wMessage = blogPostFactory
.wrapPost(groupId, wDescriptor, wTimestamp, body); .wrapPost(groupId, wDescriptor, wTimestamp, body);
meta.put(KEY_TYPE, WRAPPED_POST.getInt()); meta.put(KEY_TYPE, WRAPPED_POST.getInt());
meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed());
} else if (type == COMMENT) { } else if (type == COMMENT) {
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
byte[] wDescriptor = wGroup.getDescriptor(); byte[] wDescriptor = wGroup.getDescriptor();
@@ -593,8 +603,11 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
String name = d.getString(KEY_AUTHOR_NAME); String name = d.getString(KEY_AUTHOR_NAME);
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY); byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false);
Status authorStatus; Status authorStatus;
if (authorStatuses.containsKey(authorId)) { if (isFeedPost) {
authorStatus = Status.NONE;
} else if (authorStatuses.containsKey(authorId)) {
authorStatus = authorStatuses.get(authorId); authorStatus = authorStatuses.get(authorId);
} else { } else {
authorStatus = identityManager.getAuthorStatus(txn, authorId); authorStatus = identityManager.getAuthorStatus(txn, authorId);
@@ -611,7 +624,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
timestamp, timeReceived, author, authorStatus, read); timestamp, timeReceived, author, authorStatus, read);
} else { } else {
return new BlogPostHeader(type, groupId, id, timestamp, return new BlogPostHeader(type, groupId, id, timestamp,
timeReceived, author, authorStatus, read); timeReceived, author, authorStatus, isFeedPost, read);
} }
} }

View File

@@ -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_PARENT_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY; 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_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_TIMESTAMP;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
@@ -123,6 +124,7 @@ class BlogPostValidator extends BdfMessageValidator {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_ORIGINAL_MSG_ID, m.getId()); meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
meta.put(KEY_RSS_FEED, b.isRssFeed());
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; 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.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.Clock;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.util.StringUtils; 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.BlogManager;
import org.briarproject.briar.api.blog.BlogPost; import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -75,12 +73,12 @@ import static org.briarproject.briar.util.HtmlUtils.clean;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class FeedManagerImpl implements FeedManager, Client, EventListener { class FeedManagerImpl implements FeedManager, Client, EventListener,
BlogManager.RemoveBlogHook {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(FeedManagerImpl.class.getName()); Logger.getLogger(FeedManagerImpl.class.getName());
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
@@ -88,31 +86,33 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final BlogManager blogManager; private final BlogManager blogManager;
private final BlogPostFactory blogPostFactory; private final BlogPostFactory blogPostFactory;
private final FeedFactory feedFactory;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final Clock clock; private final Clock clock;
private final Dns noDnsLookups;
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false); private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
@Inject @Inject
FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler, FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler,
@IoExecutor Executor ioExecutor, DatabaseComponent db, @IoExecutor Executor ioExecutor, DatabaseComponent db,
ContactGroupFactory contactGroupFactory, ClientHelper clientHelper, ContactGroupFactory contactGroupFactory, ClientHelper clientHelper,
IdentityManager identityManager, BlogManager blogManager, BlogManager blogManager, BlogPostFactory blogPostFactory,
BlogPostFactory blogPostFactory, SocketFactory torSocketFactory, FeedFactory feedFactory, SocketFactory torSocketFactory,
Clock clock) { Clock clock, Dns noDnsLookups) {
this.scheduler = scheduler; this.scheduler = scheduler;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.db = db; this.db = db;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.blogManager = blogManager; this.blogManager = blogManager;
this.blogPostFactory = blogPostFactory; this.blogPostFactory = blogPostFactory;
this.feedFactory = feedFactory;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.clock = clock; this.clock = clock;
this.noDnsLookups = noDnsLookups;
} }
@Override @Override
@@ -158,21 +158,21 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
} }
@Override @Override
public void addFeed(String url, GroupId g) throws DbException, IOException { public void addFeed(String url) throws DbException, IOException {
LOG.info("Adding new RSS feed..."); // fetch syndication feed to get its metadata
SyndFeed f;
// TODO check for existing feed?
// fetch feed to get its metadata
Feed feed = new Feed(url, g, clock.currentTimeMillis());
try { try {
feed = fetchFeed(feed, false); f = fetchSyndFeed(url);
} catch (FeedException e) { } catch (FeedException e) {
throw new IOException(e); throw new IOException(e);
} }
// store feed Feed feed = feedFactory.createFeed(url, f);
// store feed and new blog
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
blogManager.addBlog(txn, feed.getBlog());
List<Feed> feeds = getFeeds(txn); List<Feed> feeds = getFeeds(txn);
feeds.add(feed); feeds.add(feed);
storeFeeds(txn, feeds); storeFeeds(txn, feeds);
@@ -181,10 +181,10 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
db.endTransaction(txn); db.endTransaction(txn);
} }
// fetch feed again, post entries this time // fetch feed again and post entries
Feed updatedFeed; Feed updatedFeed;
try { try {
updatedFeed = fetchFeed(feed, true); updatedFeed = fetchFeed(feed);
} catch (FeedException e) { } catch (FeedException e) {
throw new IOException(e); throw new IOException(e);
} }
@@ -203,27 +203,35 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
} }
@Override @Override
public void removeFeed(String url) throws DbException { public void removeFeed(Feed feed) throws DbException {
LOG.info("Removing RSS feed..."); LOG.info("Removing RSS feed...");
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
List<Feed> feeds = getFeeds(txn); // this will call removingBlog() where the feed itself gets removed
boolean found = false; blogManager.removeBlog(txn, feed.getBlog());
for (Feed feed : feeds) {
if (feed.getUrl().equals(url)) {
found = true;
feeds.remove(feed);
break;
}
}
if (!found) throw new DbException();
storeFeeds(txn, feeds);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); 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 @Override
public List<Feed> getFeeds() throws DbException { public List<Feed> getFeeds() throws DbException {
List<Feed> feeds; List<Feed> feeds;
@@ -246,7 +254,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
for (Object object : d.getList(KEY_FEEDS)) { for (Object object : d.getList(KEY_FEEDS)) {
if (!(object instanceof BdfDictionary)) if (!(object instanceof BdfDictionary))
throw new FormatException(); throw new FormatException();
feeds.add(Feed.from((BdfDictionary) object)); feeds.add(feedFactory.createFeed((BdfDictionary) object));
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
@@ -259,7 +267,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
BdfList feedList = new BdfList(); BdfList feedList = new BdfList();
for (Feed feed : feeds) { for (Feed feed : feeds) {
feedList.add(feed.toBdfDictionary()); feedList.add(feedFactory.feedToBdfDictionary(feed));
} }
BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList)); BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
try { try {
@@ -300,7 +308,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
List<Feed> newFeeds = new ArrayList<Feed>(feeds.size()); List<Feed> newFeeds = new ArrayList<Feed>(feeds.size());
for (Feed feed : feeds) { for (Feed feed : feeds) {
try { try {
newFeeds.add(fetchFeed(feed, true)); newFeeds.add(fetchFeed(feed));
} catch (FeedException e) { } catch (FeedException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -323,49 +331,52 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
LOG.info("Done updating RSS feeds"); LOG.info("Done updating RSS feeds");
} }
private Feed fetchFeed(Feed feed, boolean post) private SyndFeed fetchSyndFeed(String url)
throws FeedException, IOException, DbException { throws FeedException, IOException {
String title, description, author; // fetch feed
long updated = clock.currentTimeMillis(); SyndFeed f = getSyndFeed(getFeedInputStream(url));
long lastEntryTime = feed.getLastEntryTime();
SyndFeed f = getSyndFeed(getFeedInputStream(feed.getUrl()));
title = StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle();
if (title != null) title = clean(title, STRIP_ALL);
description = StringUtils.isNullOrEmpty(f.getDescription()) ? null :
f.getDescription();
if (description != null) description = clean(description, STRIP_ALL);
author =
StringUtils.isNullOrEmpty(f.getAuthor()) ? null : f.getAuthor();
if (author != null) author = clean(author, STRIP_ALL);
if (f.getEntries().size() == 0) if (f.getEntries().size() == 0)
throw new FeedException("Feed has no entries"); throw new FeedException("Feed has no entries");
// clean title
String title =
StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle();
if (title != null) title = clean(title, STRIP_ALL);
f.setTitle(title);
// clean description
String description =
StringUtils.isNullOrEmpty(f.getDescription()) ? null :
f.getDescription();
if (description != null) description = clean(description, STRIP_ALL);
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);
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 // sort and add new entries
if (post) { long lastEntryTime = postFeedEntries(feed, f.getEntries());
lastEntryTime = postFeedEntries(feed, f.getEntries());
} return feedFactory.createFeed(feed, f, lastEntryTime);
return new Feed(feed.getUrl(), feed.getBlogId(), title, description,
author, feed.getAdded(), updated, lastEntryTime);
} }
private InputStream getFeedInputStream(String url) throws IOException { 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 // Build HTTP Client
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(torSocketFactory) .socketFactory(torSocketFactory)
.dns(noLookups) .dns(noDnsLookups) // Don't make local DNS lookups
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS) .connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
.build(); .build();
@@ -422,9 +433,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
// build post body // build post body
StringBuilder b = new StringBuilder(); 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())) { if (!StringUtils.isNullOrEmpty(entry.getTitle())) {
b.append("<h1>").append(entry.getTitle()).append("</h1>"); b.append("<h1>").append(entry.getTitle()).append("</h1>");
} }
@@ -461,9 +471,9 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
String body = getPostBody(b.toString()); String body = getPostBody(b.toString());
try { try {
// create and store post // create and store post
LocalAuthor author = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = feed.getLocalAuthor();
BlogPost post = blogPostFactory BlogPost post = blogPostFactory
.createBlogPost(groupId, time, null, author, body); .createBlogPost(groupId, time, null, localAuthor, body);
blogManager.addLocalPost(txn, post); blogManager.addLocalPost(txn, post);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.feed;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.api.feed.FeedManager;
import javax.inject.Inject; import javax.inject.Inject;
@@ -21,11 +22,18 @@ public class FeedModule {
@Provides @Provides
@Singleton @Singleton
FeedManager provideFeedManager(FeedManagerImpl feedManager, FeedManager provideFeedManager(FeedManagerImpl feedManager,
LifecycleManager lifecycleManager, EventBus eventBus) { LifecycleManager lifecycleManager, EventBus eventBus,
BlogManager blogManager) {
lifecycleManager.registerClient(feedManager); lifecycleManager.registerClient(feedManager);
eventBus.addListener(feedManager); eventBus.addListener(feedManager);
blogManager.registerRemoveBlogHook(feedManager);
return feedManager; return feedManager;
} }
@Provides
FeedFactory provideFeedFactory(FeedFactoryImpl feedFactory) {
return feedFactory;
}
} }

View File

@@ -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);
}
}

View File

@@ -39,14 +39,20 @@ class BlogSharingValidator extends SharingValidator {
@Override @Override
protected GroupId validateDescriptor(BdfList descriptor) protected GroupId validateDescriptor(BdfList descriptor)
throws FormatException { throws FormatException {
checkSize(descriptor, 2); checkSize(descriptor, 3);
String name = descriptor.getString(0); String name = descriptor.getString(0);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = descriptor.getRaw(1); byte[] publicKey = descriptor.getRaw(1);
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH); checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
boolean rssFeed = descriptor.getBoolean(2);
Author author = authorFactory.createAuthor(name, publicKey); 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(); return blog.getId();
} }

View File

@@ -297,7 +297,7 @@ public class BlogManagerImplTest extends BriarTestCase {
final LocalAuthor localAuthor = final LocalAuthor localAuthor =
new LocalAuthor(authorId, "Author", publicKey, privateKey, new LocalAuthor(authorId, "Author", publicKey, privateKey,
created); created);
return new Blog(group, localAuthor); return new Blog(group, localAuthor, false);
} }
private BdfDictionary authorToBdfDictionary(Author a) { private BdfDictionary authorToBdfDictionary(Author a) {

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.blog; 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.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestDatabaseModule;
import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.Blog;
@@ -32,7 +34,7 @@ public class BlogManagerIntegrationTest
extends BriarIntegrationTest<BriarIntegrationTestComponent> { extends BriarIntegrationTest<BriarIntegrationTestComponent> {
private BlogManager blogManager0, blogManager1; private BlogManager blogManager0, blogManager1;
private Blog blog0, blog1; private Blog blog0, blog1, rssBlog;
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@@ -50,6 +52,12 @@ public class BlogManagerIntegrationTest
blog0 = blogFactory.createBlog(author0); blog0 = blogFactory.createBlog(author0);
blog1 = blogFactory.createBlog(author1); blog1 = blogFactory.createBlog(author1);
rssBlog = blogFactory.createFeedBlog(author0);
Transaction txn = db0.startTransaction(false);
blogManager0.addBlog(txn, rssBlog);
db0.commitTransaction(txn);
db0.endTransaction(txn);
} }
@Override @Override
@@ -393,4 +401,63 @@ public class BlogManagerIntegrationTest
assertEquals(2, headers0.size()); 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());
}
}
} }

View File

@@ -79,7 +79,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
new BdfEntry(KEY_AUTHOR_NAME, author.getName()), new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey()) new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
); );
blog = new Blog(group, author); blog = new Blog(group, author, false);
MessageId messageId = new MessageId(TestUtils.getRandomId()); MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();

View File

@@ -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());
}
}

View File

@@ -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();
}

View File

@@ -23,8 +23,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
private final byte[] publicKey = private final byte[] publicKey =
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
private final Author author = new Author(authorId, authorName, publicKey); private final Author author = new Author(authorId, authorName, publicKey);
private final Blog blog = new Blog(group, author); private final Blog blog = new Blog(group, author, false);
private final BdfList descriptor = BdfList.of(authorName, publicKey); private final BdfList descriptor = BdfList.of(authorName, publicKey, false);
private final String content = private final String content =
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH); TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
@@ -64,7 +64,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsNullBlogName() throws Exception { public void testRejectsNullBlogName() throws Exception {
BdfList invalidDescriptor = BdfList.of(null, publicKey); BdfList invalidDescriptor = BdfList.of(null, publicKey, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -72,7 +72,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsNonStringBlogName() throws Exception { public void testRejectsNonStringBlogName() throws Exception {
BdfList invalidDescriptor = BdfList.of(123, publicKey); BdfList invalidDescriptor = BdfList.of(123, publicKey, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -80,7 +80,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsTooShortBlogName() throws Exception { public void testRejectsTooShortBlogName() throws Exception {
BdfList invalidDescriptor = BdfList.of("", publicKey); BdfList invalidDescriptor = BdfList.of("", publicKey, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -89,7 +89,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test @Test
public void testAcceptsMinLengthBlogName() throws Exception { public void testAcceptsMinLengthBlogName() throws Exception {
String shortBlogName = TestUtils.getRandomString(1); String shortBlogName = TestUtils.getRandomString(1);
BdfList validDescriptor = BdfList.of(shortBlogName, publicKey); BdfList validDescriptor = BdfList.of(shortBlogName, publicKey, false);
expectCreateBlog(shortBlogName, publicKey); expectCreateBlog(shortBlogName, publicKey);
expectEncodeMetadata(INVITE); expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group, BdfMessageContext messageContext = v.validateMessage(message, group,
@@ -102,7 +102,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
public void testRejectsTooLongBlogName() throws Exception { public void testRejectsTooLongBlogName() throws Exception {
String invalidBlogName = String invalidBlogName =
TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1); TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1);
BdfList invalidDescriptor = BdfList.of(invalidBlogName, publicKey); BdfList invalidDescriptor =
BdfList.of(invalidBlogName, publicKey, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -110,7 +111,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsNullPublicKey() throws Exception { public void testRejectsNullPublicKey() throws Exception {
BdfList invalidDescriptor = BdfList.of(authorName, null); BdfList invalidDescriptor = BdfList.of(authorName, null, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -118,7 +119,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsNonRawPublicKey() throws Exception { public void testRejectsNonRawPublicKey() throws Exception {
BdfList invalidDescriptor = BdfList.of(authorName, 123); BdfList invalidDescriptor = BdfList.of(authorName, 123, false);
v.validateMessage(message, group, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -127,7 +128,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testRejectsTooLongPublicKey() throws Exception { public void testRejectsTooLongPublicKey() throws Exception {
byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1); 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, v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null)); null));
@@ -136,7 +137,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
@Test @Test
public void testAcceptsMinLengthPublicKey() throws Exception { public void testAcceptsMinLengthPublicKey() throws Exception {
byte[] key = TestUtils.getRandomBytes(1); byte[] key = TestUtils.getRandomBytes(1);
BdfList validDescriptor = BdfList.of(authorName, key); BdfList validDescriptor = BdfList.of(authorName, key, false);
expectCreateBlog(authorName, key); expectCreateBlog(authorName, key);
expectEncodeMetadata(INVITE); expectEncodeMetadata(INVITE);

View File

@@ -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;
}
}