From 306b6f30a4c416445886af33954fac20dbc29f22 Mon Sep 17 00:00:00 2001 From: orionlee Date: Thu, 24 Oct 2019 11:32:06 -0700 Subject: rename + refactor QueueSorter to FeedItemPermutors, to support both queue and podcast screen. --- .../danoeh/antennapod/core/storage/DBWriter.java | 14 +- .../antennapod/core/util/FeedItemPermutors.java | 190 +++++++++++++++++++ .../danoeh/antennapod/core/util/QueueSorter.java | 205 --------------------- 3 files changed, 199 insertions(+), 210 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java delete mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java (limited to 'core/src/main/java') diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index 912c67da3..9fe87b5d7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -37,10 +37,10 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.Permutor; -import de.danoeh.antennapod.core.util.QueueSorter; import de.danoeh.antennapod.core.util.SortOrder; /** @@ -386,7 +386,7 @@ public class DBWriter { // do not shuffle the list on every change return; } - Permutor permutor = QueueSorter.getPermutor(sortOrder); + Permutor permutor = FeedItemPermutors.getPermutor(sortOrder); permutor.reorder(queue); // Replace ADDED events by a single SORTED event @@ -846,14 +846,18 @@ public class DBWriter { } /** - * Sort the FeedItems in the queue with the given Permutor. + * Sort the FeedItems in the queue with the given the named sort order. * - * @param permutor Encapsulates whole-Queue reordering logic. * @param broadcastUpdate true if this operation should trigger a * QueueUpdateBroadcast. This option should be set to false * if the caller wants to avoid unexpected updates of the GUI. */ - public static Future reorderQueue(final Permutor permutor, final boolean broadcastUpdate) { + public static Future reorderQueue(@Nullable SortOrder sortOrder, final boolean broadcastUpdate) { + if (sortOrder == null) { + Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing."); + return dbExec.submit(() -> { }); + } + final Permutor permutor = FeedItemPermutors.getPermutor(sortOrder); return dbExec.submit(() -> { final PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java new file mode 100644 index 000000000..2d1d6e658 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java @@ -0,0 +1,190 @@ +package de.danoeh.antennapod.core.util; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; + +/** + * Provides method for sorting the a list of {@link FeedItem} according to rules. + */ +public class FeedItemPermutors { + + /** + * Returns a Permutor that sorts a list appropriate to the given sort order. + * + * @return Permutor that sorts a list appropriate to the given sort order. + */ + @NonNull + public static Permutor getPermutor(@NonNull SortOrder sortOrder) { + + Comparator comparator = null; + Permutor permutor = null; + + switch (sortOrder) { + case EPISODE_TITLE_A_Z: + comparator = (f1, f2) -> f1.getTitle().compareTo(f2.getTitle()); + break; + case EPISODE_TITLE_Z_A: + comparator = (f1, f2) -> f2.getTitle().compareTo(f1.getTitle()); + break; + case DATE_OLD_NEW: + comparator = (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate()); + break; + case DATE_NEW_OLD: + comparator = (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate()); + break; + case DURATION_SHORT_LONG: + comparator = (f1, f2) -> { + FeedMedia f1Media = f1.getMedia(); + FeedMedia f2Media = f2.getMedia(); + int duration1 = f1Media != null ? f1Media.getDuration() : -1; + int duration2 = f2Media != null ? f2Media.getDuration() : -1; + + if (duration1 == -1 || duration2 == -1) + return duration2 - duration1; + else + return duration1 - duration2; + }; + break; + case DURATION_LONG_SHORT: + comparator = (f1, f2) -> { + FeedMedia f1Media = f1.getMedia(); + FeedMedia f2Media = f2.getMedia(); + int duration1 = f1Media != null ? f1Media.getDuration() : -1; + int duration2 = f2Media != null ? f2Media.getDuration() : -1; + + return -1 * (duration1 - duration2); + }; + break; + case FEED_TITLE_A_Z: + comparator = (f1, f2) -> f1.getFeed().getTitle().compareTo(f2.getFeed().getTitle()); + break; + case FEED_TITLE_Z_A: + comparator = (f1, f2) -> f2.getFeed().getTitle().compareTo(f1.getFeed().getTitle()); + break; + case RANDOM: + permutor = Collections::shuffle; + break; + case SMART_SHUFFLE_OLD_NEW: + permutor = (queue) -> smartShuffle(queue, true); + break; + case SMART_SHUFFLE_NEW_OLD: + permutor = (queue) -> smartShuffle(queue, false); + break; + } + + if (comparator != null) { + final Comparator comparator2 = comparator; + permutor = (queue) -> Collections.sort(queue, comparator2); + } + return permutor; + } + + /** + * Implements a reordering by pubdate that avoids consecutive episodes from the same feed in + * the queue. + * + * A listener might want to hear episodes from any given feed in pubdate order, but would + * prefer a more balanced ordering that avoids having to listen to clusters of consecutive + * episodes from the same feed. This is what "Smart Shuffle" tries to accomplish. + * + * The Smart Shuffle algorithm involves spreading episodes from each feed out over the whole + * queue. To do this, we calculate the number of episodes in each feed, then a common multiple + * (not the smallest); each episode is then spread out, and we sort the resulting list of + * episodes by "spread out factor" and feed name. + * + * For example, given a queue containing three episodes each from three different feeds + * (A, B, and C), a simple pubdate sort might result in a queue that looks like the following: + * + * B1, B2, B3, A1, A2, C1, C2, C3, A3 + * + * (note that feed B episodes were all published before the first feed A episode, so a simple + * pubdate sort will often result in significant clustering of episodes from a single feed) + * + * Using Smart Shuffle, the resulting queue would look like the following: + * + * A1, B1, C1, A2, B2, C2, A3, B3, C3 + * + * (note that episodes above aren't strictly ordered in terms of pubdate, but episodes + * of each feed do appear in pubdate order) + * + * @param queue A (modifiable) list of FeedItem elements to be reordered. + * @param ascending {@code true} to use ascending pubdate in the reordering; + * {@code false} for descending. + */ + private static void smartShuffle(List queue, boolean ascending) { + + // Divide FeedItems into lists by feed + + Map> map = new HashMap<>(); + + while (!queue.isEmpty()) { + FeedItem item = queue.remove(0); + Long id = item.getFeedId(); + if (!map.containsKey(id)) { + map.put(id, new ArrayList<>()); + } + map.get(id).add(item); + } + + // Sort each individual list by PubDate (ascending/descending) + + Comparator itemComparator = ascending + ? (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate()) + : (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate()); + + // Calculate the spread + + long spread = 0; + for (Map.Entry> mapEntry : map.entrySet()) { + List feedItems = mapEntry.getValue(); + Collections.sort(feedItems, itemComparator); + if (spread == 0) { + spread = feedItems.size(); + } else if (spread % feedItems.size() != 0){ + spread *= feedItems.size(); + } + } + + // Create a list of the individual FeedItems lists, and sort it by feed title (ascending). + // Doing this ensures that the feed order we use is predictable/deterministic. + + List> feeds = new ArrayList<>(map.values()); + Collections.sort(feeds, + (f1, f2) -> f1.get(0).getFeed().getTitle().compareTo(f2.get(0).getFeed().getTitle())); + + // Spread each episode out + Map> spreadItems = new HashMap<>(); + for (List feedItems : feeds) { + long thisSpread = spread / feedItems.size(); + if (thisSpread == 0) { + thisSpread = 1; + } + // Starting from 0 ensures we front-load, so the queue starts with one episode from + // each feed in the queue + long itemSpread = 0; + for (FeedItem feedItem : feedItems) { + if (!spreadItems.containsKey(itemSpread)) { + spreadItems.put(itemSpread, new ArrayList<>()); + } + spreadItems.get(itemSpread).add(feedItem); + itemSpread += thisSpread; + } + } + + // Go through the spread items and add them to the queue + List spreads = new ArrayList<>(spreadItems.keySet()); + Collections.sort(spreads); + for (long itemSpread : spreads) { + queue.addAll(spreadItems.get(itemSpread)); + } + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java deleted file mode 100644 index 0c21ca393..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java +++ /dev/null @@ -1,205 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.storage.DBWriter; - -/** - * Provides method for sorting the queue according to rules. - */ -public class QueueSorter { - - /** - * Sorts the queue by the given sort order and sends a broadcast update. - * - * @param sortOrder Sort order. - * @param broadcastUpdate Send broadcast update? - */ - public static void sort(SortOrder sortOrder, boolean broadcastUpdate) { - Permutor permutor = getPermutor(sortOrder); - if (permutor != null) { - DBWriter.reorderQueue(permutor, broadcastUpdate); - } - } - - /** - * Returns a Permutor that sorts a list appropriate to the given sort order. - * - * @param sortOrder Sort order. - * @return Permutor that sorts a list appropriate to the given sort order. null if the order is unknown or null. - */ - public static Permutor getPermutor(SortOrder sortOrder) { - if (sortOrder == null) { - return null; - } - - Comparator comparator = null; - Permutor permutor = null; - - switch (sortOrder) { - case EPISODE_TITLE_A_Z: - comparator = (f1, f2) -> f1.getTitle().compareTo(f2.getTitle()); - break; - case EPISODE_TITLE_Z_A: - comparator = (f1, f2) -> f2.getTitle().compareTo(f1.getTitle()); - break; - case DATE_OLD_NEW: - comparator = (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate()); - break; - case DATE_NEW_OLD: - comparator = (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate()); - break; - case DURATION_SHORT_LONG: - comparator = (f1, f2) -> { - FeedMedia f1Media = f1.getMedia(); - FeedMedia f2Media = f2.getMedia(); - int duration1 = f1Media != null ? f1Media.getDuration() : -1; - int duration2 = f2Media != null ? f2Media.getDuration() : -1; - - if (duration1 == -1 || duration2 == -1) - return duration2 - duration1; - else - return duration1 - duration2; - }; - break; - case DURATION_LONG_SHORT: - comparator = (f1, f2) -> { - FeedMedia f1Media = f1.getMedia(); - FeedMedia f2Media = f2.getMedia(); - int duration1 = f1Media != null ? f1Media.getDuration() : -1; - int duration2 = f2Media != null ? f2Media.getDuration() : -1; - - return -1 * (duration1 - duration2); - }; - break; - case FEED_TITLE_A_Z: - comparator = (f1, f2) -> f1.getFeed().getTitle().compareTo(f2.getFeed().getTitle()); - break; - case FEED_TITLE_Z_A: - comparator = (f1, f2) -> f2.getFeed().getTitle().compareTo(f1.getFeed().getTitle()); - break; - case RANDOM: - permutor = Collections::shuffle; - break; - case SMART_SHUFFLE_OLD_NEW: - permutor = (queue) -> smartShuffle(queue, true); - break; - case SMART_SHUFFLE_NEW_OLD: - permutor = (queue) -> smartShuffle(queue, false); - break; - } - - if (comparator != null) { - final Comparator comparator2 = comparator; - permutor = (queue) -> Collections.sort(queue, comparator2); - } - return permutor; - } - - /** - * Implements a reordering by pubdate that avoids consecutive episodes from the same feed in - * the queue. - * - * A listener might want to hear episodes from any given feed in pubdate order, but would - * prefer a more balanced ordering that avoids having to listen to clusters of consecutive - * episodes from the same feed. This is what "Smart Shuffle" tries to accomplish. - * - * The Smart Shuffle algorithm involves spreading episodes from each feed out over the whole - * queue. To do this, we calculate the number of episodes in each feed, then a common multiple - * (not the smallest); each episode is then spread out, and we sort the resulting list of - * episodes by "spread out factor" and feed name. - * - * For example, given a queue containing three episodes each from three different feeds - * (A, B, and C), a simple pubdate sort might result in a queue that looks like the following: - * - * B1, B2, B3, A1, A2, C1, C2, C3, A3 - * - * (note that feed B episodes were all published before the first feed A episode, so a simple - * pubdate sort will often result in significant clustering of episodes from a single feed) - * - * Using Smart Shuffle, the resulting queue would look like the following: - * - * A1, B1, C1, A2, B2, C2, A3, B3, C3 - * - * (note that episodes above aren't strictly ordered in terms of pubdate, but episodes - * of each feed do appear in pubdate order) - * - * @param queue A (modifiable) list of FeedItem elements to be reordered. - * @param ascending {@code true} to use ascending pubdate in the reordering; - * {@code false} for descending. - */ - private static void smartShuffle(List queue, boolean ascending) { - - // Divide FeedItems into lists by feed - - Map> map = new HashMap<>(); - - while (!queue.isEmpty()) { - FeedItem item = queue.remove(0); - Long id = item.getFeedId(); - if (!map.containsKey(id)) { - map.put(id, new ArrayList<>()); - } - map.get(id).add(item); - } - - // Sort each individual list by PubDate (ascending/descending) - - Comparator itemComparator = ascending - ? (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate()) - : (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate()); - - // Calculate the spread - - long spread = 0; - for (Map.Entry> mapEntry : map.entrySet()) { - List feedItems = mapEntry.getValue(); - Collections.sort(feedItems, itemComparator); - if (spread == 0) { - spread = feedItems.size(); - } else if (spread % feedItems.size() != 0){ - spread *= feedItems.size(); - } - } - - // Create a list of the individual FeedItems lists, and sort it by feed title (ascending). - // Doing this ensures that the feed order we use is predictable/deterministic. - - List> feeds = new ArrayList<>(map.values()); - Collections.sort(feeds, - (f1, f2) -> f1.get(0).getFeed().getTitle().compareTo(f2.get(0).getFeed().getTitle())); - - // Spread each episode out - Map> spreadItems = new HashMap<>(); - for (List feedItems : feeds) { - long thisSpread = spread / feedItems.size(); - if (thisSpread == 0) { - thisSpread = 1; - } - // Starting from 0 ensures we front-load, so the queue starts with one episode from - // each feed in the queue - long itemSpread = 0; - for (FeedItem feedItem : feedItems) { - if (!spreadItems.containsKey(itemSpread)) { - spreadItems.put(itemSpread, new ArrayList<>()); - } - spreadItems.get(itemSpread).add(feedItem); - itemSpread += thisSpread; - } - } - - // Go through the spread items and add them to the queue - List spreads = new ArrayList<>(spreadItems.keySet()); - Collections.sort(spreads); - for (long itemSpread : spreads) { - queue.addAll(spreadItems.get(itemSpread)); - } - } -} -- cgit v1.2.3