From 85b897c7d778ae70d837ae0c2a9e7b4252ea8945 Mon Sep 17 00:00:00 2001 From: Sebastian Zeller Date: Tue, 20 Oct 2020 13:27:27 +0200 Subject: [PATCH 1/2] Filter the All Episodes tab via SQL Query Fixes #4414 --- .../fragment/AllEpisodesFragment.java | 5 +- .../fragment/EpisodesListFragment.java | 8 +++ .../antennapod/core/feed/FeedItemFilter.java | 52 +++++++++++++++++++ .../antennapod/core/storage/DBReader.java | 25 +++++++++ .../antennapod/core/storage/PodDBAdapter.java | 26 +++++++--- 5 files changed, 105 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index ae3ba3a54..08da1f8d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -105,13 +105,12 @@ public class AllEpisodesFragment extends EpisodesListFragment { @NonNull @Override protected List loadData() { - return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE)); + return DBReader.getRecentlyPublishedEpisodesFiltered(0, page * EPISODES_PER_PAGE, feedItemFilter); } @NonNull @Override protected List loadMoreData() { - return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, - EPISODES_PER_PAGE)); + return DBReader.getRecentlyPublishedEpisodesFiltered((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 8dae310ba..39f935bbe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -383,6 +383,14 @@ public abstract class EpisodesListFragment extends Fragment { @NonNull protected abstract List loadData(); + /** + * Load a new page of data as defined by {@link #page} and {@link #EPISODES_PER_PAGE}. + * If the number of items returned is less than {@link #EPISODES_PER_PAGE}, + * it will be assumed that the underlying data is exhausted + * and this method will not be called again. + * + * @return The items from the next page of data + */ @NonNull protected abstract List loadMoreData(); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java index e8e478a86..b9d79715a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.List; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.LongList; import static de.danoeh.antennapod.core.feed.FeedItem.TAG_FAVORITE; @@ -15,6 +16,7 @@ import static de.danoeh.antennapod.core.feed.FeedItem.TAG_FAVORITE; public class FeedItemFilter { private final String[] mProperties; + private final String mQuery; private boolean showPlayed = false; private boolean showUnplayed = false; @@ -78,6 +80,45 @@ public class FeedItemFilter { break; } } + + mQuery = makeQuery(); + } + + private String makeQuery() { + // The keys used within this method, but explicitly combined with their table + String keyRead = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ; + String keyPosition = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION; + String keyDownloaded = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED; + String keyMediaId = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_ID; + String keyItemId = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID; + String keyFeedItem = PodDBAdapter.KEY_FEEDITEM; + String tableQueue = PodDBAdapter.TABLE_NAME_QUEUE; + String tableFavorites = PodDBAdapter.TABLE_NAME_FAVORITES; + + List statements = new ArrayList<>(); + if (showPlayed) statements.add(keyRead + " = 1 "); + if (showUnplayed) statements.add(" NOT " + keyRead + " = 1 "); // Match "New" items (read = -1) as well + if (showPaused) statements.add(" (" + keyPosition + " NOT NULL AND " + keyPosition + " > 0 " + ") "); + if (showNotPaused) statements.add(" (" + keyPosition + " IS NULL OR " + keyPosition + " = 0 " + ") "); + if (showQueued) statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); + if (showNotQueued) statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); + if (showDownloaded) statements.add(keyDownloaded + " = 1 "); + if (showNotDownloaded) statements.add(keyDownloaded + " = 0 "); + if (showHasMedia) statements.add(keyMediaId + " NOT NULL "); + if (showNoMedia) statements.add(keyMediaId + " IS NULL "); + if (showIsFavorite) statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); + if (showNotFavorite) statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); + + if (statements.isEmpty()) { + return ""; + } + StringBuilder query = new StringBuilder(" (" + statements.get(0)); + for (String r : statements.subList(1, statements.size())) { + query.append(" AND "); + query.append(r); + } + query.append(") "); + return query.toString(); } /** @@ -125,6 +166,17 @@ public class FeedItemFilter { return result; } + /** + * Express this filter using an SQL boolean statement that can be inserted into an SQL WHERE clause + * to yield output filtered according to the rules of this filter. + * + * @return An SQL boolean statement that matches the desired items, + * empty string if there is nothing to filter + */ + public String getQuery() { + return mQuery; + } + public String[] getValues() { return mProperties.clone(); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index b218a73f9..2ba817b94 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -17,6 +17,7 @@ import java.util.Map; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.SubscriptionsFilter; @@ -386,6 +387,30 @@ public final class DBReader { } } + /** + * Loads a filtered list of FeedItems sorted by pubDate in descending order. + * + * @param offset The first episode that should be loaded. + * @param limit The maximum number of episodes that should be loaded. + * @param filter The filter describing which episodes to filter out. + */ + @NonNull + public static List getRecentlyPublishedEpisodesFiltered(int offset, int limit, + FeedItemFilter filter) { + Log.d(TAG, "getRecentlyPublishedEpisodesFiltered() called with: " + + "offset = [" + offset + "]" + " limit = [" + limit + "]" + " filter = [" + filter.getQuery() + "]"); + + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + try (Cursor cursor = adapter.getRecentlyPublishedItemsCursorFiltered(offset, limit, filter)) { + List items = extractItemlistFromCursor(adapter, cursor); + loadAdditionalFeedItemListData(items); + return items; + } finally { + adapter.close(); + } + } + /** * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode * has been completed at least once. 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 539bedd9f..a02cce504 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 @@ -31,6 +31,7 @@ import java.util.Set; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -116,14 +117,14 @@ public class PodDBAdapter { public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending"; // Table names - static final String TABLE_NAME_FEEDS = "Feeds"; - static final String TABLE_NAME_FEED_ITEMS = "FeedItems"; - static final String TABLE_NAME_FEED_IMAGES = "FeedImages"; - static final String TABLE_NAME_FEED_MEDIA = "FeedMedia"; - static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog"; - static final String TABLE_NAME_QUEUE = "Queue"; - static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters"; - static final String TABLE_NAME_FAVORITES = "Favorites"; + public static final String TABLE_NAME_FEEDS = "Feeds"; + public static final String TABLE_NAME_FEED_ITEMS = "FeedItems"; + public static final String TABLE_NAME_FEED_IMAGES = "FeedImages"; + public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia"; + public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog"; + public static final String TABLE_NAME_QUEUE = "Queue"; + public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters"; + public static final String TABLE_NAME_FAVORITES = "Favorites"; // SQL Statements for creating new tables private static final String TABLE_PRIMARY_KEY = KEY_ID @@ -1053,6 +1054,15 @@ public class PodDBAdapter { return db.rawQuery(query, null); } + public final Cursor getRecentlyPublishedItemsCursorFiltered(int offset, int limit, + FeedItemFilter filter) { + String filterQuery = filter.getQuery(); + String whereClause = "".equals(filterQuery) ? "" : " WHERE " + filter.getQuery(); + final String query = SELECT_FEED_ITEMS_AND_MEDIA + whereClause + + " ORDER BY " + KEY_PUBDATE + " DESC LIMIT " + offset + ", " + limit; + return db.rawQuery(query, null); + } + public Cursor getDownloadedItemsCursor() { final String query = SELECT_FEED_ITEMS_AND_MEDIA + "WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " > 0"; From f610ceffc2a257d5e43e5b1d728284fc2800c42c Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 22 Jan 2021 11:27:51 +0100 Subject: [PATCH 2/2] Split up filter model and database handling --- .../antennapod/dialogs/ShareDialogTest.java | 5 - .../antennapod/playback/PlaybackTest.java | 5 +- .../fragment/AllEpisodesFragment.java | 4 +- .../antennapod/core/feed/FeedItemFilter.java | 147 +++++------------- .../antennapod/core/storage/DBReader.java | 29 +--- .../antennapod/core/storage/PodDBAdapter.java | 14 +- .../storage/mapper/FeedItemFilterQuery.java | 76 +++++++++ 7 files changed, 126 insertions(+), 154 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemFilterQuery.java diff --git a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java index 8c628efd5..e31838671 100644 --- a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java +++ b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java @@ -11,15 +11,11 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; - import androidx.test.espresso.intent.rule.IntentsTestRule; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.fragment.EpisodesFragment; import de.test.antennapod.EspressoTestUtils; import de.test.antennapod.ui.UITestUtils; @@ -70,7 +66,6 @@ public class ShareDialogTest { onView(withText(R.string.all_episodes_short_label)).perform(click()); Matcher allEpisodesMatcher; - final List episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); allEpisodesMatcher = Matchers.allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2)); onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000)); onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, click())); diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java index 419cf2096..f3bd61839 100644 --- a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java @@ -10,6 +10,7 @@ import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; +import de.danoeh.antennapod.core.feed.FeedItemFilter; import org.awaitility.Awaitility; import org.hamcrest.Matcher; import org.junit.After; @@ -252,7 +253,7 @@ public class PlaybackTest { onView(isRoot()).perform(waitForView(withText(R.string.all_episodes_short_label), 1000)); onView(withText(R.string.all_episodes_short_label)).perform(click()); - final List episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); + final List episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered()); Matcher allEpisodesMatcher = allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2)); onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000)); onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton))); @@ -287,7 +288,7 @@ public class PlaybackTest { uiTestUtils.addLocalFeedData(true); DBWriter.clearQueue().get(); activityTestRule.launchActivity(new Intent()); - final List episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); + final List episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered()); startLocalPlayback(); FeedMedia media = episodes.get(0).getMedia(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index 77b272da7..612959c04 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -104,12 +104,12 @@ public class AllEpisodesFragment extends EpisodesListFragment { @NonNull @Override protected List loadData() { - return DBReader.getRecentlyPublishedEpisodesFiltered(0, page * EPISODES_PER_PAGE, feedItemFilter); + return DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE, feedItemFilter); } @NonNull @Override protected List loadMoreData() { - return DBReader.getRecentlyPublishedEpisodesFiltered((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter); + return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java index 16ab5a171..bd30a3953 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java @@ -3,127 +3,68 @@ package de.danoeh.antennapod.core.feed; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.LongList; import static de.danoeh.antennapod.core.feed.FeedItem.TAG_FAVORITE; public class FeedItemFilter { - private final String[] mProperties; - private final String mQuery; + private final String[] properties; - private boolean showPlayed = false; - private boolean showUnplayed = false; - private boolean showPaused = false; - private boolean showNotPaused = false; - private boolean showQueued = false; - private boolean showNotQueued = false; - private boolean showDownloaded = false; - private boolean showNotDownloaded = false; - private boolean showHasMedia = false; - private boolean showNoMedia = false; - private boolean showIsFavorite = false; - private boolean showNotFavorite = false; + public final boolean showPlayed; + public final boolean showUnplayed; + public final boolean showPaused; + public final boolean showNotPaused; + public final boolean showQueued; + public final boolean showNotQueued; + public final boolean showDownloaded; + public final boolean showNotDownloaded; + public final boolean showHasMedia; + public final boolean showNoMedia; + public final boolean showIsFavorite; + public final boolean showNotFavorite; + + public static FeedItemFilter unfiltered() { + return new FeedItemFilter(""); + } public FeedItemFilter(String properties) { this(TextUtils.split(properties, ",")); } public FeedItemFilter(String[] properties) { - this.mProperties = properties; - for (String property : properties) { - // see R.arrays.feed_filter_values - switch (property) { - case "unplayed": - showUnplayed = true; - break; - case "paused": - showPaused = true; - break; - case "not_paused": - showNotPaused = true; - break; - case "played": - showPlayed = true; - break; - case "queued": - showQueued = true; - break; - case "not_queued": - showNotQueued = true; - break; - case "downloaded": - showDownloaded = true; - break; - case "not_downloaded": - showNotDownloaded = true; - break; - case "has_media": - showHasMedia = true; - break; - case "no_media": - showNoMedia = true; - break; - case "is_favorite": - showIsFavorite = true; - break; - case "not_favorite": - showNotFavorite = true; - break; - default: - break; - } - } + this.properties = properties; - mQuery = makeQuery(); + // see R.arrays.feed_filter_values + showUnplayed = hasProperty("unplayed"); + showPaused = hasProperty("paused"); + showNotPaused = hasProperty("not_paused"); + showPlayed = hasProperty("played"); + showQueued = hasProperty("queued"); + showNotQueued = hasProperty("not_queued"); + showDownloaded = hasProperty("downloaded"); + showNotDownloaded = hasProperty("not_downloaded"); + showHasMedia = hasProperty("has_media"); + showNoMedia = hasProperty("no_media"); + showIsFavorite = hasProperty("is_favorite"); + showNotFavorite = hasProperty("not_favorite"); } - private String makeQuery() { - // The keys used within this method, but explicitly combined with their table - String keyRead = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ; - String keyPosition = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION; - String keyDownloaded = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED; - String keyMediaId = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_ID; - String keyItemId = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID; - String keyFeedItem = PodDBAdapter.KEY_FEEDITEM; - String tableQueue = PodDBAdapter.TABLE_NAME_QUEUE; - String tableFavorites = PodDBAdapter.TABLE_NAME_FAVORITES; - - List statements = new ArrayList<>(); - if (showPlayed) statements.add(keyRead + " = 1 "); - if (showUnplayed) statements.add(" NOT " + keyRead + " = 1 "); // Match "New" items (read = -1) as well - if (showPaused) statements.add(" (" + keyPosition + " NOT NULL AND " + keyPosition + " > 0 " + ") "); - if (showNotPaused) statements.add(" (" + keyPosition + " IS NULL OR " + keyPosition + " = 0 " + ") "); - if (showQueued) statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); - if (showNotQueued) statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); - if (showDownloaded) statements.add(keyDownloaded + " = 1 "); - if (showNotDownloaded) statements.add(keyDownloaded + " = 0 "); - if (showHasMedia) statements.add(keyMediaId + " NOT NULL "); - if (showNoMedia) statements.add(keyMediaId + " IS NULL "); - if (showIsFavorite) statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); - if (showNotFavorite) statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); - - if (statements.isEmpty()) { - return ""; - } - StringBuilder query = new StringBuilder(" (" + statements.get(0)); - for (String r : statements.subList(1, statements.size())) { - query.append(" AND "); - query.append(r); - } - query.append(") "); - return query.toString(); + private boolean hasProperty(String property) { + return Arrays.asList(properties).contains(property); } /** * Run a list of feed items through the filter. */ public List filter(List items) { - if(mProperties.length == 0) return items; + if (properties.length == 0) { + return items; + } List result = new ArrayList<>(); @@ -164,23 +105,11 @@ public class FeedItemFilter { return result; } - /** - * Express this filter using an SQL boolean statement that can be inserted into an SQL WHERE clause - * to yield output filtered according to the rules of this filter. - * - * @return An SQL boolean statement that matches the desired items, - * empty string if there is nothing to filter - */ - public String getQuery() { - return mQuery; - } - public String[] getValues() { - return mProperties.clone(); + return properties.clone(); } public boolean isShowDownloaded() { return showDownloaded; } - } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 58f838a75..ee46f4e62 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -366,27 +366,6 @@ public final class DBReader { } } - /** - * Loads a list of FeedItems sorted by pubDate in descending order. - * - * @param offset The first episode that should be loaded. - * @param limit The maximum number of episodes that should be loaded. - */ - @NonNull - public static List getRecentlyPublishedEpisodes(int offset, int limit) { - Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "offset = [" + offset + "]" + " limit = [" + limit + "]" ); - - PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - try (Cursor cursor = adapter.getRecentlyPublishedItemsCursor(offset, limit)) { - List items = extractItemlistFromCursor(adapter, cursor); - loadAdditionalFeedItemListData(items); - return items; - } finally { - adapter.close(); - } - } - /** * Loads a filtered list of FeedItems sorted by pubDate in descending order. * @@ -395,14 +374,12 @@ public final class DBReader { * @param filter The filter describing which episodes to filter out. */ @NonNull - public static List getRecentlyPublishedEpisodesFiltered(int offset, int limit, - FeedItemFilter filter) { - Log.d(TAG, "getRecentlyPublishedEpisodesFiltered() called with: " - + "offset = [" + offset + "]" + " limit = [" + limit + "]" + " filter = [" + filter.getQuery() + "]"); + public static List getRecentlyPublishedEpisodes(int offset, int limit, FeedItemFilter filter) { + Log.d(TAG, "getRecentlyPublishedEpisodes() called with: offset=" + offset + ", limit=" + limit); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); - try (Cursor cursor = adapter.getRecentlyPublishedItemsCursorFiltered(offset, limit, filter)) { + try (Cursor cursor = adapter.getRecentlyPublishedItemsCursor(offset, limit, filter)) { List items = extractItemlistFromCursor(adapter, cursor); loadAdditionalFeedItemListData(items); return items; 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 7eae8b9f0..4a06829ce 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 @@ -16,6 +16,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import de.danoeh.antennapod.core.storage.mapper.FeedItemFilterQuery; import org.apache.commons.io.FileUtils; import java.io.File; @@ -1045,16 +1046,9 @@ public class PodDBAdapter { return db.rawQuery(query, null); } - public final Cursor getRecentlyPublishedItemsCursor(int offset, int limit) { - final String query = SELECT_FEED_ITEMS_AND_MEDIA - + "ORDER BY " + KEY_PUBDATE + " DESC LIMIT " + offset + ", " + limit; - return db.rawQuery(query, null); - } - - public final Cursor getRecentlyPublishedItemsCursorFiltered(int offset, int limit, - FeedItemFilter filter) { - String filterQuery = filter.getQuery(); - String whereClause = "".equals(filterQuery) ? "" : " WHERE " + filter.getQuery(); + public final Cursor getRecentlyPublishedItemsCursor(int offset, int limit, FeedItemFilter filter) { + String filterQuery = FeedItemFilterQuery.generateFrom(filter); + String whereClause = "".equals(filterQuery) ? "" : " WHERE " + filterQuery; final String query = SELECT_FEED_ITEMS_AND_MEDIA + whereClause + " ORDER BY " + KEY_PUBDATE + " DESC LIMIT " + offset + ", " + limit; return db.rawQuery(query, null); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemFilterQuery.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemFilterQuery.java new file mode 100644 index 000000000..f6963b5ac --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemFilterQuery.java @@ -0,0 +1,76 @@ +package de.danoeh.antennapod.core.storage.mapper; + +import de.danoeh.antennapod.core.feed.FeedItemFilter; +import de.danoeh.antennapod.core.storage.PodDBAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class FeedItemFilterQuery { + private FeedItemFilterQuery() { + // Must not be instantiated + } + + /** + * Express the filter using an SQL boolean statement that can be inserted into an SQL WHERE clause + * to yield output filtered according to the rules of this filter. + * + * @return An SQL boolean statement that matches the desired items, + * empty string if there is nothing to filter + */ + public static String generateFrom(FeedItemFilter filter) { + // The keys used within this method, but explicitly combined with their table + String keyRead = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ; + String keyPosition = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION; + String keyDownloaded = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED; + String keyMediaId = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_ID; + String keyItemId = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID; + String keyFeedItem = PodDBAdapter.KEY_FEEDITEM; + String tableQueue = PodDBAdapter.TABLE_NAME_QUEUE; + String tableFavorites = PodDBAdapter.TABLE_NAME_FAVORITES; + + List statements = new ArrayList<>(); + if (filter.showPlayed) { + statements.add(keyRead + " = 1 "); + } else if (filter.showUnplayed) { + statements.add(" NOT " + keyRead + " = 1 "); // Match "New" items (read = -1) as well + } + if (filter.showPaused) { + statements.add(" (" + keyPosition + " NOT NULL AND " + keyPosition + " > 0 " + ") "); + } else if (filter.showNotPaused) { + statements.add(" (" + keyPosition + " IS NULL OR " + keyPosition + " = 0 " + ") "); + } + if (filter.showQueued) { + statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); + } else if (filter.showNotQueued) { + statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") "); + } + if (filter.showDownloaded) { + statements.add(keyDownloaded + " = 1 "); + } else if (filter.showNotDownloaded) { + statements.add(keyDownloaded + " = 0 "); + } + if (filter.showHasMedia) { + statements.add(keyMediaId + " NOT NULL "); + } else if (filter.showNoMedia) { + statements.add(keyMediaId + " IS NULL "); + } + if (filter.showIsFavorite) { + statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); + } else if (filter.showNotFavorite) { + statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") "); + } + + if (statements.isEmpty()) { + return ""; + } + + StringBuilder query = new StringBuilder(" (" + statements.get(0)); + for (String r : statements.subList(1, statements.size())) { + query.append(" AND "); + query.append(r); + } + query.append(") "); + return query.toString(); + } +}