summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorTony Tam <149837+tonytamsf@users.noreply.github.com>2021-04-06 09:15:14 -0700
committerGitHub <noreply@github.com>2021-04-06 18:15:14 +0200
commit79c79efce588b446cf9816a01e0056e7e3dfd9db (patch)
treefaa91fbd058c9fb93a827f68dac4062de3c0b04d /core
parentef41704166cc17b95159a891e7be36c8b334b8b4 (diff)
downloadAntennaPod-79c79efce588b446cf9816a01e0056e7e3dfd9db.zip
Parsing podcast:funding tag, showing payment, funding links on the show info screen (#4933)
Diffstat (limited to 'core')
-rw-r--r--core/build.gradle1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedFunding.java91
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/PodcastIndex.java38
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java3
-rw-r--r--core/src/main/res/values/strings.xml2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/syndication/handler/AtomParserTest.java4
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/syndication/handler/RssParserTest.java20
-rw-r--r--core/src/test/resources/feed-rss-testMultipleFundingTags.xml9
13 files changed, 198 insertions, 22 deletions
diff --git a/core/build.gradle b/core/build.gradle
index 75ad7faad..554847cbf 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -107,6 +107,7 @@ dependencies {
playApi "com.google.android.support:wearable:$wearableSupportVersion"
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
+ testImplementation 'androidx.test:core:1.2.0'
testImplementation "org.awaitility:awaitility:$awaitilityVersion"
testImplementation 'junit:junit:4.13'
testImplementation 'org.mockito:mockito-inline:3.5.13'
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index dd8a466eb..617997465 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -54,7 +54,7 @@ public class Feed extends FeedFile {
*/
private String lastUpdate;
- private String paymentLink;
+ private ArrayList<FeedFunding> fundingList;
/**
* Feed type, for example RSS 2 or Atom.
*/
@@ -103,8 +103,9 @@ public class Feed extends FeedFile {
/**
* This constructor is used for restoring a feed from the database.
*/
- public Feed(long id, String lastUpdate, String title, String customTitle, String link, String description, String paymentLink,
- String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
+ public Feed(long id, String lastUpdate, String title, String customTitle, String link,
+ String description, String paymentLinks, String author, String language,
+ String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded, boolean paged, String nextPageLink,
String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
@@ -114,7 +115,7 @@ public class Feed extends FeedFile {
this.lastUpdate = lastUpdate;
this.link = link;
this.description = description;
- this.paymentLink = paymentLink;
+ this.fundingList = FeedFunding.extractPaymentLinks(paymentLinks);
this.author = author;
this.language = language;
this.type = type;
@@ -237,8 +238,8 @@ public class Feed extends FeedFile {
if (other.author != null) {
author = other.author;
}
- if (other.paymentLink != null) {
- paymentLink = other.paymentLink;
+ if (other.fundingList != null) {
+ fundingList = other.fundingList;
}
// this feed's nextPage might already point to a higher page, so we only update the nextPage value
// if this feed is not paged and the other feed is.
@@ -285,8 +286,8 @@ public class Feed extends FeedFile {
return true;
}
}
- if (other.paymentLink != null) {
- if (paymentLink == null || !paymentLink.equals(other.paymentLink)) {
+ if (other.fundingList != null) {
+ if (fundingList == null || !fundingList.equals(other.fundingList)) {
return true;
}
}
@@ -390,12 +391,15 @@ public class Feed extends FeedFile {
this.feedIdentifier = feedIdentifier;
}
- public String getPaymentLink() {
- return paymentLink;
+ public void addPayment(FeedFunding funding) {
+ if (fundingList == null) {
+ fundingList = new ArrayList<FeedFunding>();
+ }
+ fundingList.add(funding);
}
- public void setPaymentLink(String paymentLink) {
- this.paymentLink = paymentLink;
+ public ArrayList<FeedFunding> getPaymentLinks() {
+ return fundingList;
}
public String getLanguage() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFunding.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFunding.java
new file mode 100644
index 000000000..151e68a4f
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFunding.java
@@ -0,0 +1,91 @@
+package de.danoeh.antennapod.core.feed;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+
+public class FeedFunding {
+ public static final String FUNDING_ENTRIES_SEPARATOR = "\u001e";
+ public static final String FUNDING_TITLE_SEPARATOR = "\u001f";
+
+ public String url;
+ public String content;
+
+ public FeedFunding(String url, String content) {
+ this.url = url;
+ this.content = content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !obj.getClass().equals(this.getClass())) {
+ return false;
+ }
+
+ FeedFunding funding = (FeedFunding) obj;
+ if (url == null && funding.url == null && content == null && funding.content == null) {
+ return true;
+ }
+ if (url != null && url.equals(funding.url) && content != null && content.equals(funding.content)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (url + FUNDING_TITLE_SEPARATOR + content).hashCode();
+ }
+
+ public static ArrayList<FeedFunding> extractPaymentLinks(String payLinks) {
+ if (StringUtils.isBlank(payLinks)) {
+ return null;
+ }
+ // old format before we started with PodcastIndex funding tag
+ ArrayList<FeedFunding> funding = new ArrayList<FeedFunding>();
+ if (!payLinks.contains(FeedFunding.FUNDING_ENTRIES_SEPARATOR)
+ && !payLinks.contains(FeedFunding.FUNDING_TITLE_SEPARATOR)) {
+ funding.add(new FeedFunding(payLinks, ""));
+ return funding;
+ }
+ String [] list = payLinks.split(FeedFunding.FUNDING_ENTRIES_SEPARATOR);
+ if (list.length == 0) {
+ return null;
+ }
+
+ for (String str : list) {
+ String [] linkContent = str.split(FeedFunding.FUNDING_TITLE_SEPARATOR);
+ if (StringUtils.isBlank(linkContent[0])) {
+ continue;
+ }
+ String url = linkContent[0];
+ String title = "";
+ if (linkContent.length > 1 && ! StringUtils.isBlank(linkContent[1])) {
+ title = linkContent[1];
+ }
+ funding.add(new FeedFunding(url, title));
+ }
+ return funding;
+ }
+
+ public static String getPaymentLinksAsString(ArrayList<FeedFunding> fundingList) {
+ StringBuilder result = new StringBuilder();
+ if (fundingList == null) {
+ return null;
+ }
+ for (FeedFunding fund : fundingList) {
+ result.append(fund.url).append(FeedFunding.FUNDING_TITLE_SEPARATOR).append(fund.content);
+ result.append(FeedFunding.FUNDING_ENTRIES_SEPARATOR);
+ }
+ return StringUtils.removeEnd(result.toString(), FeedFunding.FUNDING_ENTRIES_SEPARATOR);
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index 7ab776856..bad7775f2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -17,6 +17,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import de.danoeh.antennapod.core.feed.FeedFunding;
import de.danoeh.antennapod.core.storage.mapper.FeedItemFilterQuery;
import org.apache.commons.io.FileUtils;
@@ -403,7 +404,7 @@ public class PodDBAdapter {
values.put(KEY_TITLE, feed.getFeedTitle());
values.put(KEY_LINK, feed.getLink());
values.put(KEY_DESCRIPTION, feed.getDescription());
- values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
+ values.put(KEY_PAYMENT_LINK, FeedFunding.getPaymentLinksAsString(feed.getPaymentLinks()));
values.put(KEY_AUTHOR, feed.getAuthor());
values.put(KEY_LANGUAGE, feed.getLanguage());
values.put(KEY_IMAGE_URL, feed.getImageUrl());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
index 608cade88..7aaf66668 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
@@ -7,6 +7,7 @@ import java.util.Map;
import java.util.Stack;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedFunding;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
@@ -28,6 +29,7 @@ public class HandlerState {
final Map<String, String> alternateUrls;
private final ArrayList<FeedItem> items;
private FeedItem currentItem;
+ private FeedFunding currentFunding;
final Stack<SyndElement> tagstack;
/**
* Namespaces that have been defined so far.
@@ -78,6 +80,14 @@ public class HandlerState {
this.currentItem = currentItem;
}
+ public FeedFunding getCurrentFunding() {
+ return currentFunding;
+ }
+
+ public void setCurrentFunding(FeedFunding currentFunding) {
+ this.currentFunding = currentFunding;
+ }
+
/**
* Returns the SyndElement that comes after the top element of the tagstack.
*/
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
index ab66b912b..5beb36d6d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
@@ -14,6 +14,7 @@ import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
import de.danoeh.antennapod.core.syndication.namespace.NSSimpleChapters;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
+import de.danoeh.antennapod.core.syndication.namespace.PodcastIndex;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
@@ -107,9 +108,13 @@ class SyndHandler extends DefaultHandler {
&& prefix.equals(NSDublinCore.NSTAG)) {
state.namespaces.put(uri, new NSDublinCore());
Log.d(TAG, "Recognized DublinCore namespace");
+ } else if (uri.equals(PodcastIndex.NSURI) || uri.equals(PodcastIndex.NSURI2)
+ && prefix.equals(PodcastIndex.NSTAG)) {
+ state.namespaces.put(uri, new PodcastIndex());
+ Log.d(TAG, "Recognized PodcastIndex namespace");
}
- }
- }
+ }
+ }
private Namespace getHandlingNamespace(String uri, String qName) {
Namespace handler = state.namespaces.get(uri);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/PodcastIndex.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/PodcastIndex.java
new file mode 100644
index 000000000..eb68c0915
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/PodcastIndex.java
@@ -0,0 +1,38 @@
+package de.danoeh.antennapod.core.syndication.namespace;
+
+import org.jsoup.helper.StringUtil;
+import org.xml.sax.Attributes;
+import de.danoeh.antennapod.core.feed.FeedFunding;
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+
+public class PodcastIndex extends Namespace {
+
+ public static final String NSTAG = "podcast";
+ public static final String NSURI = "https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md";
+ public static final String NSURI2 = "https://podcastindex.org/namespace/1.0";
+ private static final String URL = "url";
+ private static final String FUNDING = "funding";
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ if (FUNDING.equals(localName)) {
+ String href = attributes.getValue(URL);
+ FeedFunding funding = new FeedFunding(href, "");
+ state.setCurrentFunding(funding);
+ state.getFeed().addPayment(state.getCurrentFunding());
+ }
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if (state.getContentBuf() == null) {
+ return;
+ }
+ String content = state.getContentBuf().toString();
+ if (FUNDING.equals(localName) && state.getCurrentFunding() != null && !StringUtil.isBlank(content)) {
+ state.getCurrentFunding().setContent(content);
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
index 42f787d98..eeca82aee 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.syndication.namespace.atom;
import android.text.TextUtils;
import android.util.Log;
+import de.danoeh.antennapod.core.feed.FeedFunding;
import de.danoeh.antennapod.core.syndication.util.SyndStringUtils;
import org.xml.sax.Attributes;
@@ -137,7 +138,7 @@ public class NSAtom extends Namespace {
//A Link such as to a directory such as iTunes
}
} else if (LINK_REL_PAYMENT.equals(rel) && state.getFeed() != null) {
- state.getFeed().setPaymentLink(href);
+ state.getFeed().addPayment(new FeedFunding(href, ""));
} else if (LINK_REL_NEXT.equals(rel) && state.getFeed() != null) {
state.getFeed().setPaged(true);
state.getFeed().setNextPageLink(href);
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 6b7ea2be4..d4e8dc7cb 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -88,6 +88,8 @@
<string name="author_label">Author(s)</string>
<string name="language_label">Language</string>
<string name="url_label">URL</string>
+ <string name="support_funding_label">Support</string>
+ <string name="support_podcast">Support this Podcast</string>
<string name="cover_label">Cover</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">An error occurred:</string>
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java
index c779b6d55..89843e941 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java
@@ -48,7 +48,7 @@ public class FeedCursorMapperTest {
assertEquals("feed custom title", feed.getCustomTitle());
assertEquals("feed link", feed.getLink());
assertEquals("feed description", feed.getDescription());
- assertEquals("feed payment link", feed.getPaymentLink());
+ assertEquals("feed payment link", feed.getPaymentLinks().get(0).url);
assertEquals("feed author", feed.getAuthor());
assertEquals("feed language", feed.getLanguage());
assertEquals("feed image url", feed.getImageUrl());
diff --git a/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/AtomParserTest.java b/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/AtomParserTest.java
index 1195bed69..5e982ed3c 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/AtomParserTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/AtomParserTest.java
@@ -30,7 +30,7 @@ public class AtomParserTest {
assertEquals("http://example.com/feed", feed.getFeedIdentifier());
assertEquals("http://example.com", feed.getLink());
assertEquals("This is the description", feed.getDescription());
- assertEquals("http://example.com/payment", feed.getPaymentLink());
+ assertEquals("http://example.com/payment", feed.getPaymentLinks().get(0).url);
assertEquals("http://example.com/picture", feed.getImageUrl());
assertEquals(10, feed.getItems().size());
for (int i = 0; i < feed.getItems().size(); i++) {
@@ -62,7 +62,7 @@ public class AtomParserTest {
assertEquals("http://example.com/feed", feed.getFeedIdentifier());
assertEquals("http://example.com", feed.getLink());
assertEquals("This is the description", feed.getDescription());
- assertEquals("http://example.com/payment", feed.getPaymentLink());
+ assertEquals("http://example.com/payment", feed.getPaymentLinks().get(0).url);
assertEquals("https://example.com/image.png", feed.getImageUrl());
assertEquals(0, feed.getItems().size());
}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/RssParserTest.java b/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/RssParserTest.java
index 95b04a511..90b14c875 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/RssParserTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/syndication/handler/RssParserTest.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.syndication.handler;
+import android.text.TextUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -31,7 +32,7 @@ public class RssParserTest {
assertEquals("en", feed.getLanguage());
assertEquals("http://example.com", feed.getLink());
assertEquals("This is the description", feed.getDescription());
- assertEquals("http://example.com/payment", feed.getPaymentLink());
+ assertEquals("http://example.com/payment", feed.getPaymentLinks().get(0).url);
assertEquals("http://example.com/picture", feed.getImageUrl());
assertEquals(10, feed.getItems().size());
for (int i = 0; i < feed.getItems().size(); i++) {
@@ -62,7 +63,7 @@ public class RssParserTest {
assertEquals("title", feed.getTitle());
assertEquals("http://example.com", feed.getLink());
assertEquals("This is the description", feed.getDescription());
- assertEquals("http://example.com/payment", feed.getPaymentLink());
+ assertEquals("http://example.com/payment", feed.getPaymentLinks().get(0).url);
assertEquals("https://example.com/image.png", feed.getImageUrl());
assertEquals(0, feed.getItems().size());
}
@@ -74,7 +75,7 @@ public class RssParserTest {
assertEquals("title", feed.getTitle());
assertEquals("http://example.com", feed.getLink());
assertEquals("This is the description", feed.getDescription());
- assertEquals("http://example.com/payment", feed.getPaymentLink());
+ assertEquals("http://example.com/payment", feed.getPaymentLinks().get(0).url);
assertNull(feed.getImageUrl());
assertEquals(1, feed.getItems().size());
FeedItem feedItem = feed.getItems().get(0);
@@ -82,4 +83,17 @@ public class RssParserTest {
assertEquals(MediaType.VIDEO, feedItem.getMedia().getMediaType());
assertEquals("https://www.example.com/file.mp4", feedItem.getMedia().getDownload_url());
}
+
+ @Test
+ public void testMultipleFundingTags() throws Exception {
+ File feedFile = FeedParserTestHelper.getFeedFile("feed-rss-testMultipleFundingTags.xml");
+ Feed feed = FeedParserTestHelper.runFeedParser(feedFile);
+ assertEquals(3, feed.getPaymentLinks().size());
+ assertEquals("Text 1", feed.getPaymentLinks().get(0).content);
+ assertEquals("https://example.com/funding1", feed.getPaymentLinks().get(0).url);
+ assertEquals("Text 2", feed.getPaymentLinks().get(1).content);
+ assertEquals("https://example.com/funding2", feed.getPaymentLinks().get(1).url);
+ assertTrue(TextUtils.isEmpty(feed.getPaymentLinks().get(2).content));
+ assertEquals("https://example.com/funding3", feed.getPaymentLinks().get(2).url);
+ }
}
diff --git a/core/src/test/resources/feed-rss-testMultipleFundingTags.xml b/core/src/test/resources/feed-rss-testMultipleFundingTags.xml
new file mode 100644
index 000000000..2535bda32
--- /dev/null
+++ b/core/src/test/resources/feed-rss-testMultipleFundingTags.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<rss version="2.0" xmlns:podcast="https://podcastindex.org/namespace/1.0">
+ <channel>
+ <title>title</title>
+ </channel>
+ <podcast:funding url="https://example.com/funding1">Text 1</podcast:funding>
+ <podcast:funding url="https://example.com/funding2">Text 2</podcast:funding>
+ <podcast:funding url="https://example.com/funding3" />
+</rss>