Store RSS feeds in a separate dedicated blog

A fake LocalAuthor is created for this new blog and stored in the feed's metadata.
This commit is contained in:
Torsten Grote
2017-04-10 12:15:48 -03:00
parent 9755cd9ab4
commit c7ff1ba974
10 changed files with 245 additions and 116 deletions

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,111 @@
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.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.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_TITLE;
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) {
if (syndFeed.getTitle() == null) syndFeed.setTitle("RSS feed");
KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
LocalAuthor localAuthor = authorFactory
.createLocalAuthor(syndFeed.getTitle(),
keyPair.getPublic().getEncoded(),
keyPair.getPrivate().getEncoded());
Blog blog = blogFactory.createBlog(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.getTitle(), 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.createBlog(localAuthor);
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, blog, localAuthor, title, 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.getTitle() != null) d.put(KEY_FEED_TITLE, feed.getTitle());
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.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;
@@ -88,9 +87,9 @@ 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 AtomicBoolean fetcherStarted = new AtomicBoolean(false);
@@ -99,8 +98,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler,
@IoExecutor Executor ioExecutor, DatabaseComponent db,
ContactGroupFactory contactGroupFactory, ClientHelper clientHelper,
IdentityManager identityManager, BlogManager blogManager,
BlogPostFactory blogPostFactory, SocketFactory torSocketFactory,
BlogManager blogManager, BlogPostFactory blogPostFactory,
FeedFactory feedFactory, SocketFactory torSocketFactory,
Clock clock) {
this.scheduler = scheduler;
@@ -108,9 +107,9 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
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;
}
@@ -158,21 +157,22 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
}
@Override
public void addFeed(String url, GroupId g) throws DbException, IOException {
LOG.info("Adding new RSS feed...");
public void addFeed(String url) throws DbException, IOException {
// TODO check for existing feed?
// fetch feed to get its metadata
Feed feed = new Feed(url, g, clock.currentTimeMillis());
// 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);
}
@@ -208,16 +208,17 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
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);
Feed feed = null;
for (Feed f : feeds) {
if (f.getUrl().equals(url)) {
feed = f;
feeds.remove(f);
break;
}
}
if (!found) throw new DbException();
if (feed == null) throw new DbException();
storeFeeds(txn, feeds);
// TODO blogManager.removeBlog(txn, feed.getBlog());
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -246,7 +247,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 +260,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 +301,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,31 +324,45 @@ 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 {
@@ -461,9 +476,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))

View File

@@ -28,4 +28,9 @@ public class FeedModule {
return feedManager;
}
@Provides
FeedFactory provideFeedFactory(FeedFactoryImpl feedFactory) {
return feedFactory;
}
}