From 79c79efce588b446cf9816a01e0056e7e3dfd9db Mon Sep 17 00:00:00 2001 From: Tony Tam <149837+tonytamsf@users.noreply.github.com> Date: Tue, 6 Apr 2021 09:15:14 -0700 Subject: Parsing podcast:funding tag, showing payment, funding links on the show info screen (#4933) --- .../java/de/danoeh/antennapod/core/feed/Feed.java | 28 ++++--- .../danoeh/antennapod/core/feed/FeedFunding.java | 91 ++++++++++++++++++++++ .../antennapod/core/storage/PodDBAdapter.java | 3 +- .../core/syndication/handler/HandlerState.java | 10 +++ .../core/syndication/handler/SyndHandler.java | 9 ++- .../core/syndication/namespace/PodcastIndex.java | 38 +++++++++ .../core/syndication/namespace/atom/NSAtom.java | 3 +- 7 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/feed/FeedFunding.java create mode 100644 core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/PodcastIndex.java (limited to 'core/src/main/java/de/danoeh') 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 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(); + } + fundingList.add(funding); } - public void setPaymentLink(String paymentLink) { - this.paymentLink = paymentLink; + public ArrayList 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 extractPaymentLinks(String payLinks) { + if (StringUtils.isBlank(payLinks)) { + return null; + } + // old format before we started with PodcastIndex funding tag + ArrayList funding = new ArrayList(); + 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 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 alternateUrls; private final ArrayList items; private FeedItem currentItem; + private FeedFunding currentFunding; final Stack 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); -- cgit v1.2.3