mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 23:29:52 +01:00
Match newly added RSS feeds to existing feeds.
This commit is contained in:
@@ -33,7 +33,7 @@ dependencies {
|
|||||||
testImplementation "org.jmock:jmock:$jmock_version"
|
testImplementation "org.jmock:jmock:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
||||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
|
testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
|
||||||
|
|
||||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
@@ -16,10 +15,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
|
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
|
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class RssFeedActivity extends BriarActivity
|
public class RssFeedActivity extends BriarActivity
|
||||||
@@ -50,13 +45,13 @@ public class RssFeedActivity extends BriarActivity
|
|||||||
viewModel.getImportResult().observeEvent(this, this::onImportResult);
|
viewModel.getImportResult().observeEvent(this, this::onImportResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onImportResult(RssFeedViewModel.ImportResult result) {
|
private void onImportResult(boolean result) {
|
||||||
if (result == IMPORTED) {
|
if (result) {
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
|
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
|
||||||
onBackPressed();
|
onBackPressed();
|
||||||
}
|
}
|
||||||
} else if (result == FAILED) {
|
} else {
|
||||||
String url = viewModel.getUrlFailedImport();
|
String url = viewModel.getUrlFailedImport();
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
@@ -65,9 +60,6 @@ public class RssFeedActivity extends BriarActivity
|
|||||||
RssFeedImportFailedDialogFragment.newInstance(url);
|
RssFeedImportFailedDialogFragment.newInstance(url);
|
||||||
dialog.show(getSupportFragmentManager(),
|
dialog.show(getSupportFragmentManager(),
|
||||||
RssFeedImportFailedDialogFragment.TAG);
|
RssFeedImportFailedDialogFragment.TAG);
|
||||||
} else if (result == EXISTS) {
|
|
||||||
Toast.makeText(this, R.string.blogs_rss_feeds_import_exists,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -37,13 +38,9 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
|
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
|
|
||||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class RssFeedViewModel extends DbViewModel {
|
class RssFeedViewModel extends DbViewModel {
|
||||||
enum ImportResult {IMPORTED, FAILED, EXISTS}
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(RssFeedViewModel.class.getName());
|
getLogger(RssFeedViewModel.class.getName());
|
||||||
@@ -59,7 +56,7 @@ class RssFeedViewModel extends DbViewModel {
|
|||||||
private volatile String urlFailedImport = null;
|
private volatile String urlFailedImport = null;
|
||||||
private final MutableLiveData<Boolean> isImporting =
|
private final MutableLiveData<Boolean> isImporting =
|
||||||
new MutableLiveData<>(false);
|
new MutableLiveData<>(false);
|
||||||
private final MutableLiveEvent<ImportResult> importResult =
|
private final MutableLiveEvent<Boolean> importResult =
|
||||||
new MutableLiveEvent<>();
|
new MutableLiveEvent<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -123,7 +120,7 @@ class RssFeedViewModel extends DbViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveEvent<ImportResult> getImportResult() {
|
LiveEvent<Boolean> getImportResult() {
|
||||||
return importResult;
|
return importResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,20 +133,23 @@ class RssFeedViewModel extends DbViewModel {
|
|||||||
urlFailedImport = null;
|
urlFailedImport = null;
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
if (exists(url)) {
|
|
||||||
importResult.postEvent(EXISTS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Feed feed = feedManager.addFeed(url);
|
Feed feed = feedManager.addFeed(url);
|
||||||
List<Feed> updated = addListItem(getList(feeds), feed);
|
// Update the feed if it was already present
|
||||||
if (updated != null) {
|
List<Feed> feedList = getList(feeds);
|
||||||
feeds.postValue(new LiveResult<>(updated));
|
if (feedList == null) feedList = new ArrayList<>();
|
||||||
|
List<Feed> updated = updateListItems(feedList,
|
||||||
|
f -> f.equals(feed), f -> feed);
|
||||||
|
// Add the feed if it wasn't already present
|
||||||
|
if (updated == null) {
|
||||||
|
feedList.add(feed);
|
||||||
|
updated = feedList;
|
||||||
}
|
}
|
||||||
importResult.postEvent(IMPORTED);
|
feeds.postValue(new LiveResult<>(updated));
|
||||||
|
importResult.postEvent(true);
|
||||||
} catch (DbException | IOException e) {
|
} catch (DbException | IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
urlFailedImport = url;
|
urlFailedImport = url;
|
||||||
importResult.postEvent(FAILED);
|
importResult.postEvent(false);
|
||||||
} finally {
|
} finally {
|
||||||
isImporting.postValue(false);
|
isImporting.postValue(false);
|
||||||
}
|
}
|
||||||
@@ -160,18 +160,4 @@ class RssFeedViewModel extends DbViewModel {
|
|||||||
String getUrlFailedImport() {
|
String getUrlFailedImport() {
|
||||||
return urlFailedImport;
|
return urlFailedImport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean exists(String url) {
|
|
||||||
List<Feed> list = getList(feeds);
|
|
||||||
if (list != null) {
|
|
||||||
for (Feed feed : list) {
|
|
||||||
// TODO: Fetch the feed and also match it against feeds that
|
|
||||||
// were imported from files?
|
|
||||||
if (url.equals(feed.getProperties().getUrl())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -516,7 +516,6 @@
|
|||||||
<string name="blogs_rss_feeds_import_button">Import</string>
|
<string name="blogs_rss_feeds_import_button">Import</string>
|
||||||
<string name="blogs_rss_feeds_import_hint">Enter the URL of the RSS feed</string>
|
<string name="blogs_rss_feeds_import_hint">Enter the URL of the RSS feed</string>
|
||||||
<string name="blogs_rss_feeds_import_error">We are sorry! There was an error importing your feed.</string>
|
<string name="blogs_rss_feeds_import_error">We are sorry! There was an error importing your feed.</string>
|
||||||
<string name="blogs_rss_feeds_import_exists">That feed is already imported.</string>
|
|
||||||
<string name="blogs_rss_feeds">RSS Feeds</string>
|
<string name="blogs_rss_feeds">RSS Feeds</string>
|
||||||
<string name="blogs_rss_feeds_manage_imported">Imported:</string>
|
<string name="blogs_rss_feeds_manage_imported">Imported:</string>
|
||||||
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ dependencies {
|
|||||||
testImplementation "org.jmock:jmock:$jmock_version"
|
testImplementation "org.jmock:jmock:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
|
||||||
|
|
||||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.briarproject.briar.api.blog.BlogPost;
|
|||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
import org.briarproject.briar.api.feed.Feed;
|
import org.briarproject.briar.api.feed.Feed;
|
||||||
import org.briarproject.briar.api.feed.FeedManager;
|
import org.briarproject.briar.api.feed.FeedManager;
|
||||||
|
import org.briarproject.briar.api.feed.RssProperties;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -90,6 +91,7 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
|
|||||||
private final BlogManager blogManager;
|
private final BlogManager blogManager;
|
||||||
private final BlogPostFactory blogPostFactory;
|
private final BlogPostFactory blogPostFactory;
|
||||||
private final FeedFactory feedFactory;
|
private final FeedFactory feedFactory;
|
||||||
|
private final FeedMatcher feedMatcher;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
||||||
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
|
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
|
||||||
@@ -105,6 +107,7 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
|
|||||||
BlogManager blogManager,
|
BlogManager blogManager,
|
||||||
BlogPostFactory blogPostFactory,
|
BlogPostFactory blogPostFactory,
|
||||||
FeedFactory feedFactory,
|
FeedFactory feedFactory,
|
||||||
|
FeedMatcher feedMatcher,
|
||||||
WeakSingletonProvider<OkHttpClient> httpClientProvider,
|
WeakSingletonProvider<OkHttpClient> httpClientProvider,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
@@ -115,6 +118,7 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
|
|||||||
this.blogManager = blogManager;
|
this.blogManager = blogManager;
|
||||||
this.blogPostFactory = blogPostFactory;
|
this.blogPostFactory = blogPostFactory;
|
||||||
this.feedFactory = feedFactory;
|
this.feedFactory = feedFactory;
|
||||||
|
this.feedMatcher = feedMatcher;
|
||||||
this.httpClientProvider = httpClientProvider;
|
this.httpClientProvider = httpClientProvider;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
@@ -163,16 +167,28 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
|
|||||||
public Feed addFeed(String url) throws DbException, IOException {
|
public Feed addFeed(String url) throws DbException, IOException {
|
||||||
// fetch feed to get posts and metadata
|
// fetch feed to get posts and metadata
|
||||||
SyndFeed sf = fetchSyndFeed(url);
|
SyndFeed sf = fetchSyndFeed(url);
|
||||||
|
RssProperties properties = new RssProperties(url, sf.getTitle(),
|
||||||
|
sf.getDescription(), sf.getAuthor(), sf.getLink(), sf.getUri());
|
||||||
|
|
||||||
Feed feed = feedFactory.createFeed(url, sf);
|
// check whether the properties match an existing feed
|
||||||
|
List<Feed> candidates = db.transactionWithResult(true, this::getFeeds);
|
||||||
|
Feed matched = feedMatcher.findMatchingFeed(properties, candidates);
|
||||||
|
|
||||||
// store feed metadata and new blog
|
Feed feed;
|
||||||
db.transaction(false, txn -> {
|
if (matched == null) {
|
||||||
blogManager.addBlog(txn, feed.getBlog());
|
LOG.info("Adding new feed");
|
||||||
List<Feed> feeds = getFeeds(txn);
|
feed = feedFactory.createFeed(url, sf);
|
||||||
feeds.add(feed);
|
// store feed metadata and new blog
|
||||||
storeFeeds(txn, feeds);
|
db.transaction(false, txn -> {
|
||||||
});
|
blogManager.addBlog(txn, feed.getBlog());
|
||||||
|
List<Feed> feeds = getFeeds(txn);
|
||||||
|
feeds.add(feed);
|
||||||
|
storeFeeds(txn, feeds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
LOG.info("New feed matches an existing feed");
|
||||||
|
feed = matched;
|
||||||
|
}
|
||||||
|
|
||||||
// post entries
|
// post entries
|
||||||
long lastEntryTime = postFeedEntries(feed, sf.getEntries());
|
long lastEntryTime = postFeedEntries(feed, sf.getEntries());
|
||||||
@@ -359,7 +375,7 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long postFeedEntries(Feed feed, List<SyndEntry> entries)
|
private long postFeedEntries(Feed feed, List<SyndEntry> entries)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
|
||||||
return db.transactionWithResult(false, txn -> {
|
return db.transactionWithResult(false, txn -> {
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import org.briarproject.nullsafety.NotNullByDefault;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class FeedMatcherImpl implements FeedMatcher {
|
class FeedMatcherImpl implements FeedMatcher {
|
||||||
|
|
||||||
private static final int MIN_MATCHING_FIELDS = 2;
|
private static final int MIN_MATCHING_FIELDS = 2;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedMatcherImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Feed findMatchingFeed(RssProperties candidate, List<Feed> feeds) {
|
public Feed findMatchingFeed(RssProperties candidate, List<Feed> feeds) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.feed;
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
import com.rometools.rome.feed.synd.SyndEntry;
|
import com.rometools.rome.feed.synd.SyndFeed;
|
||||||
import com.rometools.rome.feed.synd.SyndEntryImpl;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
@@ -29,19 +28,18 @@ import org.briarproject.briar.api.feed.RssProperties;
|
|||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
import okhttp3.Dns;
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -61,14 +59,10 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final BlogPostFactory blogPostFactory =
|
private final BlogPostFactory blogPostFactory =
|
||||||
context.mock(BlogPostFactory.class);
|
context.mock(BlogPostFactory.class);
|
||||||
private final FeedFactory feedFactory = context.mock(FeedFactory.class);
|
private final FeedFactory feedFactory = context.mock(FeedFactory.class);
|
||||||
|
private final FeedMatcher feedMatcher = context.mock(FeedMatcher.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
private final Clock clock = context.mock(Clock.class);
|
||||||
private final Dns noDnsLookups = context.mock(Dns.class);
|
|
||||||
|
|
||||||
private final OkHttpClient client = new OkHttpClient.Builder()
|
private final OkHttpClient client = new OkHttpClient.Builder().build();
|
||||||
.socketFactory(SocketFactory.getDefault())
|
|
||||||
.dns(noDnsLookups)
|
|
||||||
.connectTimeout(60_000, MILLISECONDS)
|
|
||||||
.build();
|
|
||||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
|
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
|
||||||
new WeakSingletonProvider<OkHttpClient>() {
|
new WeakSingletonProvider<OkHttpClient>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -84,15 +78,20 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final GroupId blogGroupId = blogGroup.getId();
|
private final GroupId blogGroupId = blogGroup.getId();
|
||||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||||
private final Blog blog = new Blog(blogGroup, localAuthor, true);
|
private final Blog blog = new Blog(blogGroup, localAuthor, true);
|
||||||
private final RssProperties properties = new RssProperties(
|
private final Message message = getMessage(blogGroupId);
|
||||||
"http://example.org", null, null, null, null, null);
|
private final BlogPost blogPost = new BlogPost(message, null, localAuthor);
|
||||||
private final Feed feed = new Feed(blog, localAuthor, properties, 0, 0, 0);
|
|
||||||
private final BdfDictionary feedDict = new BdfDictionary();
|
private final long now = System.currentTimeMillis();
|
||||||
|
// Round the publication date to a whole second to avoid rounding errors
|
||||||
|
private final long pubDate = now / 1000 * 1000 - 1000;
|
||||||
|
private final SimpleDateFormat sdf =
|
||||||
|
new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss Z");
|
||||||
|
private final String pubDateString = sdf.format(new Date(pubDate));
|
||||||
|
|
||||||
private final FeedManagerImpl feedManager =
|
private final FeedManagerImpl feedManager =
|
||||||
new FeedManagerImpl(scheduler, ioExecutor, db, contactGroupFactory,
|
new FeedManagerImpl(scheduler, ioExecutor, db, contactGroupFactory,
|
||||||
clientHelper, blogManager, blogPostFactory, feedFactory,
|
clientHelper, blogManager, blogPostFactory, feedFactory,
|
||||||
httpClientProvider, clock);
|
feedMatcher, httpClientProvider, clock);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFetchFeedsReturnsEarlyIfTorIsNotActive() {
|
public void testFetchFeedsReturnsEarlyIfTorIsNotActive() {
|
||||||
@@ -101,52 +100,166 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyFetchFeeds() throws Exception {
|
public void testFetchFeedsEmptyList() throws Exception {
|
||||||
BdfList feedList = new BdfList();
|
// The list of feeds is empty
|
||||||
expectGetFeeds(feedList);
|
expectGetFeeds();
|
||||||
expectStoreFeed(feedList);
|
expectStoreFeeds();
|
||||||
feedManager.setTorActive(true);
|
feedManager.setTorActive(true);
|
||||||
feedManager.fetchFeeds();
|
feedManager.fetchFeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFetchFeedsIoException() throws Exception {
|
public void testFetchFeedsIoException() throws Exception {
|
||||||
BdfDictionary feedDict = new BdfDictionary();
|
// Fetching the feed will fail
|
||||||
BdfList feedList = BdfList.of(feedDict);
|
MockWebServer server = new MockWebServer();
|
||||||
|
String url = server.url("/").toString();
|
||||||
|
server.enqueue(new MockResponse()
|
||||||
|
.setBody(" ")
|
||||||
|
.setSocketPolicy(DISCONNECT_DURING_RESPONSE_BODY));
|
||||||
|
|
||||||
expectGetFeeds(feedList);
|
Feed feed = createFeed(url, blog);
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(noDnsLookups).lookup("example.org");
|
expectGetFeeds(feed);
|
||||||
will(throwException(new UnknownHostException()));
|
expectStoreFeeds(feed);
|
||||||
}});
|
|
||||||
expectStoreFeed(feedList);
|
|
||||||
|
|
||||||
feedManager.setTorActive(true);
|
feedManager.setTorActive(true);
|
||||||
feedManager.fetchFeeds();
|
feedManager.fetchFeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPostFeedEntriesEmptyDate() throws Exception {
|
public void testFetchFeedsEmptyResponseBody() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
// Fetching the feed will succeed, but parsing the empty body will fail
|
||||||
List<SyndEntry> entries = new ArrayList<>();
|
MockWebServer server = new MockWebServer();
|
||||||
entries.add(new SyndEntryImpl());
|
String url = server.url("/").toString();
|
||||||
SyndEntry entry = new SyndEntryImpl();
|
server.enqueue(new MockResponse());
|
||||||
entry.setUpdatedDate(new Date());
|
|
||||||
entries.add(entry);
|
Feed feed = createFeed(url, blog);
|
||||||
String text = "<p>(" + entry.getUpdatedDate().toString() + ")</p>";
|
|
||||||
Message msg = getMessage(blogGroupId);
|
expectGetFeeds(feed);
|
||||||
BlogPost post = new BlogPost(msg, null, localAuthor);
|
expectStoreFeeds(feed);
|
||||||
|
|
||||||
|
feedManager.setTorActive(true);
|
||||||
|
feedManager.fetchFeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFetchFeedsNoEntries() throws Exception {
|
||||||
|
// Fetching and parsing the feed will succeed; there are no entries
|
||||||
|
String feedXml = createRssFeedXml();
|
||||||
|
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
String url = server.url("/").toString();
|
||||||
|
server.enqueue(new MockResponse().setBody(feedXml));
|
||||||
|
|
||||||
|
Feed feed = createFeed(url, blog);
|
||||||
|
|
||||||
|
expectGetFeeds(feed);
|
||||||
|
expectUpdateFeedNoEntries(feed);
|
||||||
|
expectStoreFeeds(feed);
|
||||||
|
|
||||||
|
feedManager.setTorActive(true);
|
||||||
|
feedManager.fetchFeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFetchFeedsOneEntry() throws Exception {
|
||||||
|
// Fetching and parsing the feed will succeed; there is one entry
|
||||||
|
String entryXml =
|
||||||
|
"<item><pubDate>" + pubDateString + "</pubDate></item>";
|
||||||
|
String feedXml = createRssFeedXml(entryXml);
|
||||||
|
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
String url = server.url("/").toString();
|
||||||
|
server.enqueue(new MockResponse().setBody(feedXml));
|
||||||
|
|
||||||
|
Feed feed = createFeed(url, blog);
|
||||||
|
|
||||||
|
expectGetFeeds(feed);
|
||||||
|
expectUpdateFeedOneEntry(feed);
|
||||||
|
expectStoreFeeds(feed);
|
||||||
|
|
||||||
|
feedManager.setTorActive(true);
|
||||||
|
feedManager.fetchFeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddNewFeed() throws Exception {
|
||||||
|
// Fetching and parsing the feed will succeed; there are no entries
|
||||||
|
String feedXml = createRssFeedXml();
|
||||||
|
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
String url = server.url("/").toString();
|
||||||
|
server.enqueue(new MockResponse().setBody(feedXml));
|
||||||
|
|
||||||
|
Feed newFeed = createFeed(url, blog);
|
||||||
|
|
||||||
|
Group existingBlogGroup = getGroup(BlogManager.CLIENT_ID,
|
||||||
|
BlogManager.MAJOR_VERSION);
|
||||||
|
Blog existingBlog = new Blog(existingBlogGroup, localAuthor, true);
|
||||||
|
Feed existingFeed = createFeed("http://example.com", existingBlog);
|
||||||
|
|
||||||
|
expectGetFeeds(existingFeed);
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
|
// The added feed doesn't match any existing feed
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(feedMatcher).findMatchingFeed(with(any(RssProperties.class)),
|
||||||
will(returnValue(42L));
|
with(singletonList(existingFeed)));
|
||||||
oneOf(blogPostFactory).createBlogPost(feed.getBlogId(), 42L, null,
|
will(returnValue(null));
|
||||||
localAuthor, text);
|
// Create the new feed
|
||||||
will(returnValue(post));
|
oneOf(feedFactory).createFeed(with(url), with(any(SyndFeed.class)));
|
||||||
oneOf(blogManager).addLocalPost(txn, post);
|
will(returnValue(newFeed));
|
||||||
|
// Add the new feed to the list of feeds
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(blogManager).addBlog(txn, blog);
|
||||||
|
expectGetFeeds(txn, existingFeed);
|
||||||
|
expectStoreFeeds(txn, existingFeed, newFeed);
|
||||||
}});
|
}});
|
||||||
feedManager.postFeedEntries(feed, entries);
|
|
||||||
|
expectUpdateFeedNoEntries(newFeed);
|
||||||
|
expectGetAndStoreFeeds(existingFeed, newFeed);
|
||||||
|
|
||||||
|
feedManager.addFeed(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddExistingFeed() throws Exception {
|
||||||
|
// Fetching and parsing the feed will succeed; there are no entries
|
||||||
|
String feedXml = createRssFeedXml();
|
||||||
|
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
String url = server.url("/").toString();
|
||||||
|
server.enqueue(new MockResponse().setBody(feedXml));
|
||||||
|
|
||||||
|
Feed newFeed = createFeed(url, blog);
|
||||||
|
|
||||||
|
expectGetFeeds(newFeed);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// The added feed matches an existing feed
|
||||||
|
oneOf(feedMatcher).findMatchingFeed(with(any(RssProperties.class)),
|
||||||
|
with(singletonList(newFeed)));
|
||||||
|
will(returnValue(newFeed));
|
||||||
|
}});
|
||||||
|
|
||||||
|
expectUpdateFeedNoEntries(newFeed);
|
||||||
|
expectGetAndStoreFeeds(newFeed);
|
||||||
|
|
||||||
|
feedManager.addFeed(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Feed createFeed(String url, Blog blog) {
|
||||||
|
RssProperties properties = new RssProperties(url,
|
||||||
|
null, null, null, null, null);
|
||||||
|
return new Feed(blog, localAuthor, properties, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createRssFeedXml(String... entries) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("<rss version='2.0'><channel>");
|
||||||
|
for (String entry : entries) sb.append(entry);
|
||||||
|
sb.append("</channel></rss>");
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectGetLocalGroup() {
|
private void expectGetLocalGroup() {
|
||||||
@@ -157,35 +270,96 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectGetFeeds(BdfList feedList) throws Exception {
|
private void expectGetFeeds(Feed... feeds) throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
|
}});
|
||||||
|
expectGetFeeds(txn, feeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectGetFeeds(Transaction txn, Feed... feeds)
|
||||||
|
throws Exception {
|
||||||
|
BdfList feedList = new BdfList();
|
||||||
|
for (int i = 0; i < feeds.length; i++) {
|
||||||
|
feedList.add(new BdfDictionary());
|
||||||
|
}
|
||||||
BdfDictionary feedsDict =
|
BdfDictionary feedsDict =
|
||||||
BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
||||||
expectGetLocalGroup();
|
expectGetLocalGroup();
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
context.checking(new Expectations() {{
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, localGroupId);
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, localGroupId);
|
||||||
will(returnValue(feedsDict));
|
will(returnValue(feedsDict));
|
||||||
if (feedList.size() == 1) {
|
for (int i = 0; i < feeds.length; i++) {
|
||||||
oneOf(feedFactory).createFeed(feedDict);
|
oneOf(feedFactory).createFeed(feedList.getDictionary(i));
|
||||||
will(returnValue(feed));
|
will(returnValue(feeds[i]));
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectStoreFeed(BdfList feedList) throws Exception {
|
private void expectStoreFeeds(Feed... feeds) throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
}});
|
||||||
|
expectStoreFeeds(txn, feeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectStoreFeeds(Transaction txn, Feed... feeds)
|
||||||
|
throws Exception {
|
||||||
|
BdfList feedList = new BdfList();
|
||||||
|
for (int i = 0; i < feeds.length; i++) {
|
||||||
|
feedList.add(new BdfDictionary());
|
||||||
|
}
|
||||||
BdfDictionary feedDict =
|
BdfDictionary feedDict =
|
||||||
BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
||||||
expectGetLocalGroup();
|
expectGetLocalGroup();
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
context.checking(new Expectations() {{
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroupId, feedDict);
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroupId, feedDict);
|
||||||
if (feedList.size() == 1) {
|
for (int i = 0; i < feeds.length; i++) {
|
||||||
oneOf(feedFactory).feedToBdfDictionary(feed);
|
oneOf(feedFactory).feedToBdfDictionary(feeds[i]);
|
||||||
will(returnValue(feedList.getDictionary(0)));
|
will(returnValue(feedList.getDictionary(i)));
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectGetAndStoreFeeds(Feed... feeds) throws Exception {
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
expectGetFeeds(txn, feeds);
|
||||||
|
expectStoreFeeds(txn, feeds);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectUpdateFeedNoEntries(Feed feed) throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
|
||||||
|
oneOf(feedFactory).updateFeed(with(feed), with(any(SyndFeed.class)),
|
||||||
|
with(0L));
|
||||||
|
will(returnValue(feed));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectUpdateFeedOneEntry(Feed feed) throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
String body = "<p>(" + new Date(pubDate) + ")</p>";
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(blogPostFactory).createBlogPost(blogGroupId, pubDate, null,
|
||||||
|
localAuthor, body);
|
||||||
|
will(returnValue(blogPost));
|
||||||
|
oneOf(blogManager).addLocalPost(txn, blogPost);
|
||||||
|
oneOf(feedFactory).updateFeed(with(feed), with(any(SyndFeed.class)),
|
||||||
|
with(pubDate));
|
||||||
|
will(returnValue(feed));
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ buildscript {
|
|||||||
bouncy_castle_version = '1.71'
|
bouncy_castle_version = '1.71'
|
||||||
junit_version = "4.13.2"
|
junit_version = "4.13.2"
|
||||||
jmock_version = '2.12.0'
|
jmock_version = '2.12.0'
|
||||||
|
mockwebserver_version = '4.9.3'
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||||
|
|||||||
Reference in New Issue
Block a user