From f610ceffc2a257d5e43e5b1d728284fc2800c42c Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 22 Jan 2021 11:27:51 +0100 Subject: Split up filter model and database handling --- .../test/antennapod/dialogs/ShareDialogTest.java | 5 - .../de/test/antennapod/playback/PlaybackTest.java | 5 +- .../antennapod/fragment/AllEpisodesFragment.java | 4 +- .../antennapod/core/feed/FeedItemFilter.java | 151 ++++++--------------- .../danoeh/antennapod/core/storage/DBReader.java | 29 +--- .../antennapod/core/storage/PodDBAdapter.java | 14 +- .../core/storage/mapper/FeedItemFilterQuery.java | 76 +++++++++++ 7 files changed, 128 insertions(+), 156 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 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; + private final String[] properties; + + 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; - } - } - - mQuery = makeQuery(); + this.properties = properties; + + // 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(); + } +} -- cgit v1.2.3