mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
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:
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,6 +48,10 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
return body;
|
||||
}
|
||||
|
||||
public boolean isRssFeed() {
|
||||
return header.isRssFeed();
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
briar-android/src/main/res/drawable/ic_rss_feed.xml
Normal file
25
briar-android/src/main/res/drawable/ic_rss_feed.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
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);
|
||||
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");
|
||||
|
||||
// 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
|
||||
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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user