summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java95
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java109
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DBWriterTest.java335
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java330
4 files changed, 439 insertions, 430 deletions
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 1ba58f3d3..57db6123c 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
@@ -398,101 +398,6 @@ public class DBWriter {
events.add(QueueEvent.sorted(queue));
}
- @VisibleForTesting
- static class ItemEnqueuePositionCalculator {
-
- public static class Options {
- private boolean enqueueAtFront = false;
- private boolean keepInProgressAtFront = false;
-
- public boolean isEnqueueAtFront() {
- return enqueueAtFront;
- }
-
- public Options setEnqueueAtFront(boolean enqueueAtFront) {
- this.enqueueAtFront = enqueueAtFront;
- return this;
- }
-
- public boolean isKeepInProgressAtFront() {
- return keepInProgressAtFront;
- }
-
- public Options setKeepInProgressAtFront(boolean keepInProgressAtFront) {
- this.keepInProgressAtFront = keepInProgressAtFront;
- return this;
- }
- }
-
- private final @NonNull Options options;
-
- /**
- * The logic needs to use {@link DownloadRequester#isDownloadingFile(FeedFile)} method only
- */
- @VisibleForTesting
- FeedFileDownloadStatusRequesterInterface requester = DownloadRequester.getInstance();
-
- public ItemEnqueuePositionCalculator(@NonNull Options options) {
- this.options = options;
- }
-
- /**
- *
- * @param positionAmongToAdd Typically, the callers has a list of items to be inserted to
- * the queue. This parameter indicates the position (0-based) of
- * the item among the one to inserted. E.g., it is needed for
- * enqueue at front option.
- *
- * @param item the item to be inserted
- * @param curQueue the queue to which the item is to be inserted
- * @return the position (0-based) the item should be inserted to the named queu
- */
- public int calcPosition(int positionAmongToAdd, FeedItem item, List<FeedItem> curQueue) {
- if (options.isEnqueueAtFront()) {
- if (options.isKeepInProgressAtFront() &&
- curQueue.size() > 0 &&
- curQueue.get(0).getMedia() != null &&
- curQueue.get(0).getMedia().isInProgress()) {
- // leave the front in progress item at the front
- return getPositionOf1stNonDownloadingItem(positionAmongToAdd + 1, curQueue);
- } else { // typical case
- // return NOT 0, so that when a list of items are inserted, the items inserted
- // keep the same order. Returning 0 will reverse the order
- return getPositionOf1stNonDownloadingItem(positionAmongToAdd, curQueue);
- }
- } else {
- return curQueue.size();
- }
- }
-
- private int getPositionOf1stNonDownloadingItem(int startPosition, List<FeedItem> curQueue) {
- final int curQueueSize = curQueue.size();
- for (int i = startPosition; i < curQueueSize; i++) {
- if (!isItemAtPositionDownloading(i, curQueue)) {
- return i;
- } // else continue to search;
- }
- return curQueueSize;
- }
-
- private boolean isItemAtPositionDownloading(int position, List<FeedItem> curQueue) {
- FeedItem curItem;
- try {
- curItem = curQueue.get(position);
- } catch (IndexOutOfBoundsException e) {
- curItem = null;
- }
-
- if (curItem != null &&
- curItem.getMedia() != null &&
- requester.isDownloadingFile(curItem.getMedia())) {
- return true;
- } else {
- return false;
- }
- }
- }
-
/**
* Removes all FeedItem objects from the queue.
*/
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
new file mode 100644
index 000000000..6e7843836
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
@@ -0,0 +1,109 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+/**
+ * @see DBWriter#addQueueItem(Context, boolean, long...) it uses the class to determine
+ * the positions of the {@link FeedItem} in the queue.
+ */
+class ItemEnqueuePositionCalculator {
+
+ public static class Options {
+ private boolean enqueueAtFront = false;
+ private boolean keepInProgressAtFront = false;
+
+ public boolean isEnqueueAtFront() {
+ return enqueueAtFront;
+ }
+
+ public Options setEnqueueAtFront(boolean enqueueAtFront) {
+ this.enqueueAtFront = enqueueAtFront;
+ return this;
+ }
+
+ public boolean isKeepInProgressAtFront() {
+ return keepInProgressAtFront;
+ }
+
+ public Options setKeepInProgressAtFront(boolean keepInProgressAtFront) {
+ this.keepInProgressAtFront = keepInProgressAtFront;
+ return this;
+ }
+ }
+
+ private final @NonNull
+ Options options;
+
+ /**
+ * The logic needs to use {@link DownloadRequester#isDownloadingFile(FeedFile)} method only
+ */
+ @VisibleForTesting
+ FeedFileDownloadStatusRequesterInterface requester = DownloadRequester.getInstance();
+
+ public ItemEnqueuePositionCalculator(@NonNull Options options) {
+ this.options = options;
+ }
+
+ /**
+ *
+ * @param positionAmongToAdd Typically, the callers has a list of items to be inserted to
+ * the queue. This parameter indicates the position (0-based) of
+ * the item among the one to inserted. E.g., it is needed for
+ * enqueue at front option.
+ *
+ * @param item the item to be inserted
+ * @param curQueue the queue to which the item is to be inserted
+ * @return the position (0-based) the item should be inserted to the named queu
+ */
+ public int calcPosition(int positionAmongToAdd, FeedItem item, List<FeedItem> curQueue) {
+ if (options.isEnqueueAtFront()) {
+ if (options.isKeepInProgressAtFront() &&
+ curQueue.size() > 0 &&
+ curQueue.get(0).getMedia() != null &&
+ curQueue.get(0).getMedia().isInProgress()) {
+ // leave the front in progress item at the front
+ return getPositionOf1stNonDownloadingItem(positionAmongToAdd + 1, curQueue);
+ } else { // typical case
+ // return NOT 0, so that when a list of items are inserted, the items inserted
+ // keep the same order. Returning 0 will reverse the order
+ return getPositionOf1stNonDownloadingItem(positionAmongToAdd, curQueue);
+ }
+ } else {
+ return curQueue.size();
+ }
+ }
+
+ private int getPositionOf1stNonDownloadingItem(int startPosition, List<FeedItem> curQueue) {
+ final int curQueueSize = curQueue.size();
+ for (int i = startPosition; i < curQueueSize; i++) {
+ if (!isItemAtPositionDownloading(i, curQueue)) {
+ return i;
+ } // else continue to search;
+ }
+ return curQueueSize;
+ }
+
+ private boolean isItemAtPositionDownloading(int position, List<FeedItem> curQueue) {
+ FeedItem curItem;
+ try {
+ curItem = curQueue.get(position);
+ } catch (IndexOutOfBoundsException e) {
+ curItem = null;
+ }
+
+ if (curItem != null &&
+ curItem.getMedia() != null &&
+ requester.isDownloadingFile(curItem.getMedia())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DBWriterTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DBWriterTest.java
deleted file mode 100644
index 46822de81..000000000
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DBWriterTest.java
+++ /dev/null
@@ -1,335 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import de.danoeh.antennapod.core.feed.FeedFile;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.feed.FeedMother;
-import de.danoeh.antennapod.core.storage.DBWriter.ItemEnqueuePositionCalculator;
-import de.danoeh.antennapod.core.storage.DBWriter.ItemEnqueuePositionCalculator.Options;
-
-import static org.junit.Assert.assertEquals;
-
-public class DBWriterTest {
-
- public static class ItemEnqueuePositionCalculatorTest {
-
- @RunWith(Parameterized.class)
- public static class IEPCBasicTest {
- @Parameters(name = "{index}: case<{0}>, expected:{1}")
- public static Iterable<Object[]> data() {
- Options optDefault = new Options();
- Options optEnqAtFront = new Options().setEnqueueAtFront(true);
-
- return Arrays.asList(new Object[][]{
- {"case default, i.e., add to the end",
- concat(QUEUE_DEFAULT_IDS, TFI_ID),
- optDefault, 0, QUEUE_DEFAULT},
- {"case default (2nd item)",
- concat(QUEUE_DEFAULT_IDS, TFI_ID),
- optDefault, 1, QUEUE_DEFAULT},
- {"case option enqueue at front",
- concat(TFI_ID, QUEUE_DEFAULT_IDS),
- optEnqAtFront, 0, QUEUE_DEFAULT},
- {"case option enqueue at front (2nd item)",
- list(11L, TFI_ID, 12L, 13L, 14L),
- optEnqAtFront, 1, QUEUE_DEFAULT},
- {"case empty queue, option default",
- list(TFI_ID),
- optDefault, 0, QUEUE_EMPTY},
- {"case empty queue, option enqueue at front",
- list(TFI_ID),
- optEnqAtFront, 0, QUEUE_EMPTY},
- });
- }
-
- @Parameter
- public String message;
-
- @Parameter(1)
- public List<Long> idsExpected;
-
- @Parameter(2)
- public Options options;
-
- @Parameter(3)
- public int posAmongAdded; // the position of feed item to be inserted among the list to be inserted.
-
- @Parameter(4)
- public List<FeedItem> curQueue;
-
-
- public static final long TFI_ID = 101;
-
- /**
- * Add a FeedItem with ID {@link #TFI_ID} with the setup
- */
- @Test
- public void test() {
- ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
-
- // shallow copy to which the test will add items
- List<FeedItem> queue = new ArrayList<>(curQueue);
- FeedItem tFI = tFI(TFI_ID);
- doAddToQueueAndAssertResult(message,
- calculator, posAmongAdded, tFI, queue,
- idsExpected);
- }
-
- }
-
- @RunWith(Parameterized.class)
- public static class IEPCKeepInProgressAtFrontTest extends IEPCBasicTest {
- @Parameters(name = "{index}: case<{0}>, expected:{1}")
- public static Iterable<Object[]> data() {
- Options optKeepInProgressAtFront =
- new Options().setEnqueueAtFront(true).setKeepInProgressAtFront(true);
- // edge case: keep in progress without enabling enqueue at front is meaningless
- Options optKeepInProgressAtFrontWithNoEnqueueAtFront =
- new Options().setKeepInProgressAtFront(true);
-
- return Arrays.asList(new Object[][]{
- {"case option keep in progress at front",
- list(11L, TFI_ID, 12L, 13L),
- optKeepInProgressAtFront, 0, QUEUE_FRONT_IN_PROGRESS},
- {"case option keep in progress at front (2nd item)",
- list(11L, 12L, TFI_ID, 13L),
- optKeepInProgressAtFront, 1, QUEUE_FRONT_IN_PROGRESS},
- {"case option keep in progress at front, front item not in progress",
- concat(TFI_ID, QUEUE_DEFAULT_IDS),
- optKeepInProgressAtFront, 0, QUEUE_DEFAULT},
- {"case option keep in progress at front, front item no media at all",
- concat(TFI_ID, QUEUE_FRONT_NO_MEDIA_IDS),
- optKeepInProgressAtFront, 0, QUEUE_FRONT_NO_MEDIA}, // No media should not cause any exception
- {"case option keep in progress at front, but enqueue at front is disabled",
- concat(QUEUE_FRONT_IN_PROGRESS_IDS, TFI_ID),
- optKeepInProgressAtFrontWithNoEnqueueAtFront, 0, QUEUE_FRONT_IN_PROGRESS},
- {"case empty queue, option keep in progress at front",
- list(TFI_ID),
- optKeepInProgressAtFront, 0, QUEUE_EMPTY},
- });
- }
-
- private static final List<FeedItem> QUEUE_FRONT_IN_PROGRESS = Arrays.asList(tFI(11, 60000), tFI(12), tFI(13));
- private static final List<Long> QUEUE_FRONT_IN_PROGRESS_IDS = toIDs(QUEUE_FRONT_IN_PROGRESS);
-
- private static final List<FeedItem> QUEUE_FRONT_NO_MEDIA = Arrays.asList(tFINoMedia(11), tFI(12), tFI(13));
- private static final List<Long> QUEUE_FRONT_NO_MEDIA_IDS = toIDs(QUEUE_FRONT_NO_MEDIA);
-
- }
-
- @RunWith(Parameterized.class)
- public static class ItemEnqueuePositionCalculatorPreserveDownloadOrderTest {
-
- @Parameters(name = "{index}: case<{0}>")
- public static Iterable<Object[]> data() {
- Options optDefault = new Options();
- Options optEnqAtFront = new Options().setEnqueueAtFront(true);
-
- // Attempts to make test more readable by showing the expected list of ids
- // (rather than the expected positions)
- return Arrays.asList(new Object[][] {
- {"download order test, enqueue default",
- concat(QUEUE_DEFAULT_IDS, 101L),
- concat(QUEUE_DEFAULT_IDS, list(101L, 102L)),
- concat(QUEUE_DEFAULT_IDS, list(101L, 102L, 201L)),
- concat(QUEUE_DEFAULT_IDS, list(101L, 102L, 201L, 202L)),
- optDefault, QUEUE_DEFAULT},
- {"download order test, enqueue at front",
- concat(101L, QUEUE_DEFAULT_IDS),
- concat(list(101L, 102L), QUEUE_DEFAULT_IDS),
- concat(list(101L, 102L, 201L), QUEUE_DEFAULT_IDS),
- concat(list(101L, 102L, 201L, 202L), QUEUE_DEFAULT_IDS),
- optEnqAtFront, QUEUE_DEFAULT},
- });
- }
-
- @Parameter
- public String message;
-
- @Parameter(1)
- public List<Long> idsExpectedAfter101;
-
- @Parameter(2)
- public List<Long> idsExpectedAfter102;
-
- // 2XX are for testing bulk insertion cases
- @Parameter(3)
- public List<Long> idsExpectedAfter201;
-
- @Parameter(4)
- public List<Long> idsExpectedAfter202;
-
- @Parameter(5)
- public Options options;
-
- @Parameter(6)
- public List<FeedItem> queueInitial;
-
- @Test
- public void testQueueOrderWhenDownloading2Items() {
-
- // Setup class under test
- //
- ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
- MockDownloadRequester mockDownloadRequester = new MockDownloadRequester();
- calculator.requester = mockDownloadRequester;
-
- // Setup initial data
- // A shallow copy, as the test code will manipulate the queue
- List<FeedItem> queue = new ArrayList<>(queueInitial);
-
-
- // Test body
-
- // User clicks download on feed item 101
- FeedItem tFI101 = tFI_isDownloading(101, mockDownloadRequester);
- doAddToQueueAndAssertResult(message + " (1st download)",
- calculator, 0, tFI101, queue,
- idsExpectedAfter101);
-
- // Then user clicks download on feed item 102
- FeedItem tFI102 = tFI_isDownloading(102, mockDownloadRequester);
- doAddToQueueAndAssertResult(message + " (2nd download, it should preserve order of download)",
- calculator, 0, tFI102, queue,
- idsExpectedAfter102);
-
- // Items 201 and 202 are added as part of a single DBWriter.addQueueItem() calls
-
- FeedItem tFI201 = tFI_isDownloading(201, mockDownloadRequester);
- doAddToQueueAndAssertResult(message + " (bulk insertion, 1st item)",
- calculator, 0, tFI201, queue,
- idsExpectedAfter201);
-
- FeedItem tFI202 = tFI_isDownloading(202, mockDownloadRequester);
- doAddToQueueAndAssertResult(message + " (bulk insertion, 2nd item)",
- calculator, 1, tFI202, queue,
- idsExpectedAfter202);
-
- // TODO: simulate download failure cases.
- }
-
-
- private static FeedItem tFI_isDownloading(int id, MockDownloadRequester requester) {
- FeedItem item = tFI(id);
- FeedMedia media =
- new FeedMedia(item, "http://download.url.net/" + id
- , 100000 + id, "audio/mp3");
- media.setId(item.getId());
- item.setMedia(media);
-
- requester.mockDownloadingFile(media, true);
-
- return item;
- }
-
- private static class MockDownloadRequester implements FeedFileDownloadStatusRequesterInterface {
-
- private Map<Long, Boolean> downloadingByIds = new HashMap<>();
-
- @Override
- public synchronized boolean isDownloadingFile(@NonNull FeedFile item) {
- return downloadingByIds.getOrDefault(item.getId(), false);
- }
-
- // All other parent methods should not be called
-
- public void mockDownloadingFile(FeedFile item, boolean isDownloading) {
- downloadingByIds.put(item.getId(), isDownloading);
- }
- }
- }
-
-
- // Common helpers:
- // - common queue (of items) for tests
- // - construct FeedItems for tests
-
- static void doAddToQueueAndAssertResult(String message,
- ItemEnqueuePositionCalculator calculator,
- int positionAmongAdd,
- FeedItem itemToAdd,
- List<FeedItem> queue,
- List<Long> idsExpected) {
- int posActual = calculator.calcPosition(positionAmongAdd, itemToAdd, queue);
- queue.add(posActual, itemToAdd);
- assertEquals(message, idsExpected, toIDs(queue));
- }
-
- static final List<FeedItem> QUEUE_EMPTY = Collections.unmodifiableList(Arrays.asList());
-
- static final List<FeedItem> QUEUE_DEFAULT = Collections.unmodifiableList(Arrays.asList(tFI(11), tFI(12), tFI(13), tFI(14)));
- static final List<Long> QUEUE_DEFAULT_IDS = QUEUE_DEFAULT.stream().map(fi -> fi.getId()).collect(Collectors.toList());
-
-
- static FeedItem tFI(long id) {
- return tFI(id, -1);
- }
-
- static FeedItem tFI(long id, int position) {
- FeedItem item = tFINoMedia(id);
- FeedMedia media = new FeedMedia(item, "download_url", 1234567, "audio/mpeg");
- media.setId(item.getId());
- item.setMedia(media);
-
- if (position >= 0) {
- media.setPosition(position);
- }
-
- return item;
- }
-
- static FeedItem tFINoMedia(long id) {
- FeedItem item = new FeedItem(id, "Item" + id, "ItemId" + id, "url",
- new Date(), FeedItem.PLAYED, FeedMother.anyFeed());
- return item;
- }
-
- // Collections helpers
-
- static <T> List<? extends T> concat(T item, List<? extends T> list) {
- List<T> res = new ArrayList<>(list);
- res.add(0, item);
- return res;
- }
-
- static <T> List<? extends T> concat(List<? extends T> list, T item) {
- List<T> res = new ArrayList<>(list);
- res.add(item);
- return res;
- }
-
- static <T> List<? extends T> concat(List<? extends T> list1, List<? extends T> list2) {
- List<T> res = new ArrayList<>(list1);
- res.addAll(list2);
- return res;
- }
-
- public static <T> List<T> list(T... a) {
- return Arrays.asList(a);
- }
-
-
- static List<Long> toIDs(List<FeedItem> items) {
- return items.stream().map(i->i.getId()).collect(Collectors.toList());
- }
-
- }
-
-}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
new file mode 100644
index 000000000..1331df67d
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
@@ -0,0 +1,330 @@
+package de.danoeh.antennapod.core.storage;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import de.danoeh.antennapod.core.feed.FeedFile;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedMother;
+import de.danoeh.antennapod.core.storage.ItemEnqueuePositionCalculator.Options;
+
+import static org.junit.Assert.assertEquals;
+
+public class ItemEnqueuePositionCalculatorTest {
+
+ @RunWith(Parameterized.class)
+ public static class IEPCBasicTest {
+ @Parameters(name = "{index}: case<{0}>, expected:{1}")
+ public static Iterable<Object[]> data() {
+ Options optDefault = new Options();
+ Options optEnqAtFront = new Options().setEnqueueAtFront(true);
+
+ return Arrays.asList(new Object[][]{
+ {"case default, i.e., add to the end",
+ concat(QUEUE_DEFAULT_IDS, TFI_ID),
+ optDefault, 0, QUEUE_DEFAULT},
+ {"case default (2nd item)",
+ concat(QUEUE_DEFAULT_IDS, TFI_ID),
+ optDefault, 1, QUEUE_DEFAULT},
+ {"case option enqueue at front",
+ concat(TFI_ID, QUEUE_DEFAULT_IDS),
+ optEnqAtFront, 0, QUEUE_DEFAULT},
+ {"case option enqueue at front (2nd item)",
+ list(11L, TFI_ID, 12L, 13L, 14L),
+ optEnqAtFront, 1, QUEUE_DEFAULT},
+ {"case empty queue, option default",
+ list(TFI_ID),
+ optDefault, 0, QUEUE_EMPTY},
+ {"case empty queue, option enqueue at front",
+ list(TFI_ID),
+ optEnqAtFront, 0, QUEUE_EMPTY},
+ });
+ }
+
+ @Parameter
+ public String message;
+
+ @Parameter(1)
+ public List<Long> idsExpected;
+
+ @Parameter(2)
+ public Options options;
+
+ @Parameter(3)
+ public int posAmongAdded; // the position of feed item to be inserted among the list to be inserted.
+
+ @Parameter(4)
+ public List<FeedItem> curQueue;
+
+
+ public static final long TFI_ID = 101;
+
+ /**
+ * Add a FeedItem with ID {@link #TFI_ID} with the setup
+ */
+ @Test
+ public void test() {
+ ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
+
+ // shallow copy to which the test will add items
+ List<FeedItem> queue = new ArrayList<>(curQueue);
+ FeedItem tFI = tFI(TFI_ID);
+ doAddToQueueAndAssertResult(message,
+ calculator, posAmongAdded, tFI, queue,
+ idsExpected);
+ }
+
+ }
+
+ @RunWith(Parameterized.class)
+ public static class IEPCKeepInProgressAtFrontTest extends IEPCBasicTest {
+ @Parameters(name = "{index}: case<{0}>, expected:{1}")
+ public static Iterable<Object[]> data() {
+ Options optKeepInProgressAtFront =
+ new Options().setEnqueueAtFront(true).setKeepInProgressAtFront(true);
+ // edge case: keep in progress without enabling enqueue at front is meaningless
+ Options optKeepInProgressAtFrontWithNoEnqueueAtFront =
+ new Options().setKeepInProgressAtFront(true);
+
+ return Arrays.asList(new Object[][]{
+ {"case option keep in progress at front",
+ list(11L, TFI_ID, 12L, 13L),
+ optKeepInProgressAtFront, 0, QUEUE_FRONT_IN_PROGRESS},
+ {"case option keep in progress at front (2nd item)",
+ list(11L, 12L, TFI_ID, 13L),
+ optKeepInProgressAtFront, 1, QUEUE_FRONT_IN_PROGRESS},
+ {"case option keep in progress at front, front item not in progress",
+ concat(TFI_ID, QUEUE_DEFAULT_IDS),
+ optKeepInProgressAtFront, 0, QUEUE_DEFAULT},
+ {"case option keep in progress at front, front item no media at all",
+ concat(TFI_ID, QUEUE_FRONT_NO_MEDIA_IDS),
+ optKeepInProgressAtFront, 0, QUEUE_FRONT_NO_MEDIA}, // No media should not cause any exception
+ {"case option keep in progress at front, but enqueue at front is disabled",
+ concat(QUEUE_FRONT_IN_PROGRESS_IDS, TFI_ID),
+ optKeepInProgressAtFrontWithNoEnqueueAtFront, 0, QUEUE_FRONT_IN_PROGRESS},
+ {"case empty queue, option keep in progress at front",
+ list(TFI_ID),
+ optKeepInProgressAtFront, 0, QUEUE_EMPTY},
+ });
+ }
+
+ private static final List<FeedItem> QUEUE_FRONT_IN_PROGRESS = Arrays.asList(tFI(11, 60000), tFI(12), tFI(13));
+ private static final List<Long> QUEUE_FRONT_IN_PROGRESS_IDS = toIDs(QUEUE_FRONT_IN_PROGRESS);
+
+ private static final List<FeedItem> QUEUE_FRONT_NO_MEDIA = Arrays.asList(tFINoMedia(11), tFI(12), tFI(13));
+ private static final List<Long> QUEUE_FRONT_NO_MEDIA_IDS = toIDs(QUEUE_FRONT_NO_MEDIA);
+
+ }
+
+ @RunWith(Parameterized.class)
+ public static class ItemEnqueuePositionCalculatorPreserveDownloadOrderTest {
+
+ @Parameters(name = "{index}: case<{0}>")
+ public static Iterable<Object[]> data() {
+ Options optDefault = new Options();
+ Options optEnqAtFront = new Options().setEnqueueAtFront(true);
+
+ // Attempts to make test more readable by showing the expected list of ids
+ // (rather than the expected positions)
+ return Arrays.asList(new Object[][] {
+ {"download order test, enqueue default",
+ concat(QUEUE_DEFAULT_IDS, 101L),
+ concat(QUEUE_DEFAULT_IDS, list(101L, 102L)),
+ concat(QUEUE_DEFAULT_IDS, list(101L, 102L, 201L)),
+ concat(QUEUE_DEFAULT_IDS, list(101L, 102L, 201L, 202L)),
+ optDefault, QUEUE_DEFAULT},
+ {"download order test, enqueue at front",
+ concat(101L, QUEUE_DEFAULT_IDS),
+ concat(list(101L, 102L), QUEUE_DEFAULT_IDS),
+ concat(list(101L, 102L, 201L), QUEUE_DEFAULT_IDS),
+ concat(list(101L, 102L, 201L, 202L), QUEUE_DEFAULT_IDS),
+ optEnqAtFront, QUEUE_DEFAULT},
+ });
+ }
+
+ @Parameter
+ public String message;
+
+ @Parameter(1)
+ public List<Long> idsExpectedAfter101;
+
+ @Parameter(2)
+ public List<Long> idsExpectedAfter102;
+
+ // 2XX are for testing bulk insertion cases
+ @Parameter(3)
+ public List<Long> idsExpectedAfter201;
+
+ @Parameter(4)
+ public List<Long> idsExpectedAfter202;
+
+ @Parameter(5)
+ public Options options;
+
+ @Parameter(6)
+ public List<FeedItem> queueInitial;
+
+ @Test
+ public void testQueueOrderWhenDownloading2Items() {
+
+ // Setup class under test
+ //
+ ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
+ MockDownloadRequester mockDownloadRequester = new MockDownloadRequester();
+ calculator.requester = mockDownloadRequester;
+
+ // Setup initial data
+ // A shallow copy, as the test code will manipulate the queue
+ List<FeedItem> queue = new ArrayList<>(queueInitial);
+
+
+ // Test body
+
+ // User clicks download on feed item 101
+ FeedItem tFI101 = tFI_isDownloading(101, mockDownloadRequester);
+ doAddToQueueAndAssertResult(message + " (1st download)",
+ calculator, 0, tFI101, queue,
+ idsExpectedAfter101);
+
+ // Then user clicks download on feed item 102
+ FeedItem tFI102 = tFI_isDownloading(102, mockDownloadRequester);
+ doAddToQueueAndAssertResult(message + " (2nd download, it should preserve order of download)",
+ calculator, 0, tFI102, queue,
+ idsExpectedAfter102);
+
+ // Items 201 and 202 are added as part of a single DBWriter.addQueueItem() calls
+
+ FeedItem tFI201 = tFI_isDownloading(201, mockDownloadRequester);
+ doAddToQueueAndAssertResult(message + " (bulk insertion, 1st item)",
+ calculator, 0, tFI201, queue,
+ idsExpectedAfter201);
+
+ FeedItem tFI202 = tFI_isDownloading(202, mockDownloadRequester);
+ doAddToQueueAndAssertResult(message + " (bulk insertion, 2nd item)",
+ calculator, 1, tFI202, queue,
+ idsExpectedAfter202);
+
+ // TODO: simulate download failure cases.
+ }
+
+
+ private static FeedItem tFI_isDownloading(int id, MockDownloadRequester requester) {
+ FeedItem item = tFI(id);
+ FeedMedia media =
+ new FeedMedia(item, "http://download.url.net/" + id
+ , 100000 + id, "audio/mp3");
+ media.setId(item.getId());
+ item.setMedia(media);
+
+ requester.mockDownloadingFile(media, true);
+
+ return item;
+ }
+
+ private static class MockDownloadRequester implements FeedFileDownloadStatusRequesterInterface {
+
+ private Map<Long, Boolean> downloadingByIds = new HashMap<>();
+
+ @Override
+ public synchronized boolean isDownloadingFile(@NonNull FeedFile item) {
+ return downloadingByIds.getOrDefault(item.getId(), false);
+ }
+
+ // All other parent methods should not be called
+
+ public void mockDownloadingFile(FeedFile item, boolean isDownloading) {
+ downloadingByIds.put(item.getId(), isDownloading);
+ }
+ }
+ }
+
+
+ // Common helpers:
+ // - common queue (of items) for tests
+ // - construct FeedItems for tests
+
+ static void doAddToQueueAndAssertResult(String message,
+ ItemEnqueuePositionCalculator calculator,
+ int positionAmongAdd,
+ FeedItem itemToAdd,
+ List<FeedItem> queue,
+ List<Long> idsExpected) {
+ int posActual = calculator.calcPosition(positionAmongAdd, itemToAdd, queue);
+ queue.add(posActual, itemToAdd);
+ assertEquals(message, idsExpected, toIDs(queue));
+ }
+
+ static final List<FeedItem> QUEUE_EMPTY = Collections.unmodifiableList(Arrays.asList());
+
+ static final List<FeedItem> QUEUE_DEFAULT = Collections.unmodifiableList(Arrays.asList(tFI(11), tFI(12), tFI(13), tFI(14)));
+ static final List<Long> QUEUE_DEFAULT_IDS = QUEUE_DEFAULT.stream().map(fi -> fi.getId()).collect(Collectors.toList());
+
+
+ static FeedItem tFI(long id) {
+ return tFI(id, -1);
+ }
+
+ static FeedItem tFI(long id, int position) {
+ FeedItem item = tFINoMedia(id);
+ FeedMedia media = new FeedMedia(item, "download_url", 1234567, "audio/mpeg");
+ media.setId(item.getId());
+ item.setMedia(media);
+
+ if (position >= 0) {
+ media.setPosition(position);
+ }
+
+ return item;
+ }
+
+ static FeedItem tFINoMedia(long id) {
+ FeedItem item = new FeedItem(id, "Item" + id, "ItemId" + id, "url",
+ new Date(), FeedItem.PLAYED, FeedMother.anyFeed());
+ return item;
+ }
+
+ // Collections helpers
+
+ static <T> List<? extends T> concat(T item, List<? extends T> list) {
+ List<T> res = new ArrayList<>(list);
+ res.add(0, item);
+ return res;
+ }
+
+ static <T> List<? extends T> concat(List<? extends T> list, T item) {
+ List<T> res = new ArrayList<>(list);
+ res.add(item);
+ return res;
+ }
+
+ static <T> List<? extends T> concat(List<? extends T> list1, List<? extends T> list2) {
+ List<T> res = new ArrayList<>(list1);
+ res.addAll(list2);
+ return res;
+ }
+
+ static <T> List<T> list(T... a) {
+ return Arrays.asList(a);
+ }
+
+
+ static List<Long> toIDs(List<FeedItem> items) {
+ return items.stream().map(i->i.getId()).collect(Collectors.toList());
+ }
+
+}