diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java index 2b29393703dcb5f57a618ccb4167cf4367054b4a..3aeaab5bd806fad1f5f3f08d8555bfeb4f04cfbd 100644 --- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java @@ -291,8 +291,12 @@ class FeedManagerImpl implements FeedManager, Client, EventListener, * This method is called periodically from a background service. * It fetches all available feeds and posts new entries to the respective * blog. + * + * We can not do this within one database {@link Transaction}, + * because fetching can take a long time + * and we can not block the database that long. */ - private void fetchFeeds() { + void fetchFeeds() { LOG.info("Updating RSS feeds..."); // Get current feeds @@ -313,12 +317,15 @@ class FeedManagerImpl implements FeedManager, Client, EventListener, } catch (FeedException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + newFeeds.add(feed); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + newFeeds.add(feed); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + newFeeds.add(feed); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d8618425113e640b40c8ebf5197d33e4d9c14ba4 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java @@ -0,0 +1,135 @@ +package org.briarproject.briar.feed; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.ImmediateExecutor; +import org.briarproject.briar.api.blog.Blog; +import org.briarproject.briar.api.blog.BlogManager; +import org.briarproject.briar.api.blog.BlogPostFactory; +import org.briarproject.briar.api.feed.Feed; +import org.jmock.Expectations; +import org.junit.Test; + +import java.net.UnknownHostException; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; + +import javax.net.SocketFactory; + +import okhttp3.Dns; + +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEEDS; +import static org.briarproject.briar.api.feed.FeedManager.CLIENT_ID; + +public class FeedManagerImplTest extends BrambleMockTestCase { + + private final ScheduledExecutorService scheduler = + context.mock(ScheduledExecutorService.class); + private final Executor ioExecutor = new ImmediateExecutor(); + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final BlogManager blogManager = context.mock(BlogManager.class); + private final BlogPostFactory blogPostFactory = + context.mock(BlogPostFactory.class); + private final FeedFactory feedFactory = context.mock(FeedFactory.class); + private final Clock clock = context.mock(Clock.class); + private final Dns noDnsLookups = context.mock(Dns.class); + + private final GroupId localGroupId = new GroupId(getRandomId()); + private final Group localGroup = + new Group(localGroupId, CLIENT_ID, getRandomBytes(42)); + private final GroupId blogGroupId = new GroupId(getRandomId()); + private final Group blogGroup = + new Group(blogGroupId, BlogManager.CLIENT_ID, getRandomBytes(42)); + private final AuthorId authorId = new AuthorId(getRandomId()); + private final LocalAuthor localAuthor = + new LocalAuthor(authorId, "author", getRandomBytes(2), + getRandomBytes(2), 0); + private final Blog blog = new Blog(blogGroup, localAuthor, true); + private final Feed feed = + new Feed("http://example.org", blog, localAuthor, 0); + private final BdfDictionary feedDict = new BdfDictionary(); + + private final FeedManagerImpl feedManager = + new FeedManagerImpl(scheduler, ioExecutor, db, contactGroupFactory, + clientHelper, blogManager, blogPostFactory, feedFactory, + SocketFactory.getDefault(), clock, noDnsLookups); + + @Test + public void testEmptyFetchFeed() throws Exception { + BdfList feedList = new BdfList(); + expectGetFeeds(feedList); + expectStoreFeed(feedList); + feedManager.fetchFeeds(); + } + + @Test + public void testFetchFeedIoException() throws Exception { + final BdfDictionary feedDict= new BdfDictionary(); + BdfList feedList = BdfList.of(feedDict); + + expectGetFeeds(feedList); + context.checking(new Expectations() {{ + oneOf(noDnsLookups).lookup("example.org"); + will(throwException(new UnknownHostException())); + }}); + expectStoreFeed(feedList); + + feedManager.fetchFeeds(); + } + + private void expectGetLocalGroup() { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID); + will(returnValue(localGroup)); + }}); + } + + private void expectGetFeeds(final BdfList feedList) throws Exception { + final Transaction txn = new Transaction(null, true); + final BdfDictionary feedsDict = + BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList)); + expectGetLocalGroup(); + context.checking(new Expectations() {{ + oneOf(db).startTransaction(true); + will(returnValue(txn)); + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, localGroupId); + will(returnValue(feedsDict)); + if (feedList.size() == 1) { + oneOf(feedFactory).createFeed(feedDict); + will(returnValue(feed)); + } + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + } + + private void expectStoreFeed(final BdfList feedList) throws Exception { + final BdfDictionary feedDict = + BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList)); + expectGetLocalGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).mergeGroupMetadata(localGroupId, feedDict); + if (feedList.size() == 1) { + oneOf(feedFactory).feedToBdfDictionary(feed); + will(returnValue(feedList.getDictionary(0))); + } + }}); + } + +}