summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorMartin Fietz <marf@hadiko-99-4.hadiko.uni-karlsruhe.de>2015-05-08 16:02:02 +0200
committerMartin Fietz <Martin.Fietz@gmail.com>2015-05-19 11:58:37 +0200
commit6f5d23c55743bd98800148c943880ce00d04441a (patch)
tree8ef9f5115d47bfba8173b9834475bdf61c7efc10 /core/src
parent406dab0a24543f4332b338f64689948cdc96c3bc (diff)
downloadAntennaPod-6f5d23c55743bd98800148c943880ce00d04441a.zip
Order feeds by number of unread items (descending)
Diffstat (limited to 'core/src')
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/util/LongLongMapTest.java59
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java62
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java45
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java252
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java1
5 files changed, 404 insertions, 15 deletions
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/util/LongLongMapTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/util/LongLongMapTest.java
new file mode 100644
index 000000000..d75cd5c77
--- /dev/null
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/util/LongLongMapTest.java
@@ -0,0 +1,59 @@
+package de.danoeh.antennapod.core.util;
+
+import android.test.AndroidTestCase;
+
+public class LongLongMapTest extends AndroidTestCase {
+
+ public void testEmptyMap() {
+ LongIntMap map = new LongIntMap();
+ assertEquals(0, map.size());
+ assertEquals("LongLongMap{}", map.toString());
+ assertEquals(0, map.get(42));
+ assertEquals(-1, map.get(42, -1));
+ assertEquals(false, map.delete(42));
+ assertEquals(-1, map.indexOfKey(42));
+ assertEquals(-1, map.indexOfValue(42));
+ assertEquals(1, map.hashCode());
+ }
+
+ public void testSingleElement() {
+ LongIntMap map = new LongIntMap();
+ map.put(17, 42);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=42}", map.toString());
+ assertEquals(42, map.get(17));
+ assertEquals(42, map.get(17, -1));
+ assertEquals(0, map.indexOfKey(17));
+ assertEquals(0, map.indexOfValue(42));
+ assertEquals(true, map.delete(17));
+ }
+
+ public void testAddAndDelete() {
+ LongIntMap map = new LongIntMap();
+ for(int i=0; i < 100; i++) {
+ map.put(i * 17, i * 42);
+ }
+ assertEquals(100, map.size());
+ assertEquals(0, map.get(0));
+ assertEquals(42, map.get(17));
+ assertEquals(42, map.get(17, -1));
+ assertEquals(1, map.indexOfKey(17));
+ assertEquals(1, map.indexOfValue(42));
+ for(int i=0; i < 100; i++) {
+ assertEquals(true, map.delete(i * 17));
+ }
+ }
+
+ public void testOverwrite() {
+ LongIntMap map = new LongIntMap();
+ map.put(17, 42);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=42}", map.toString());
+ assertEquals(42, map.get(17));
+ map.put(17, 23);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=23}", map.toString());
+ assertEquals(23, map.get(17));
+ }
+
+}
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 4d93ab447..0958aa7b9 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
@@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -23,6 +24,7 @@ import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
@@ -490,6 +492,29 @@ public final class DBReader {
}
/**
+ * Loads a list of FeedItems that are considered new.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems that are considered new.
+ */
+ public static List<FeedItem> getNewItemsList(Context context) {
+ Log.d(TAG, "getNewItemsList()");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getNewItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
+ itemlistCursor.close();
+
+ loadFeedDataOfFeedItemlist(context, items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ /**
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
*
* @param context A context that is used for opening a database connection.
@@ -966,15 +991,15 @@ public final class DBReader {
}
/**
- * Returns the number of unread items.
+ * Returns a map containing the number of unread items per feed
*
* @param context A context that is used for opening a database connection.
- * @return The number of unread items.
+ * @return The number of unread items per feed.
*/
- public static int getNumberOfUnreadItems(final Context context, long feedId) {
+ public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- final int result = adapter.getNumberOfUnreadItems(feedId);
+ final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
adapter.close();
return result;
}
@@ -1103,9 +1128,31 @@ public final class DBReader {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> feeds = getFeedList(adapter);
+ long[] feedIds = new long[feeds.size()];
+ for(int i=0; i < feeds.size(); i++) {
+ feedIds[i] = feeds.get(i).getId();
+ }
+ final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds);
+ Collections.sort(feeds, new Comparator<Feed>() {
+ @Override
+ public int compare(Feed lhs, Feed rhs) {
+ long numUnreadLhs = numUnreadFeedItems.get(lhs.getId());
+ Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items");
+ long numUnreadRhs = numUnreadFeedItems.get(rhs.getId());
+ Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items");
+ if(numUnreadLhs > numUnreadRhs) {
+ // reverse natural order: podcast with most unplayed episodes first
+ return -1;
+ } else if(numUnreadLhs == numUnreadRhs) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ });
int queueSize = adapter.getQueueSize();
int numUnreadItems = adapter.getNumberOfUnreadItems();
- NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
+ NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems, numUnreadFeedItems);
adapter.close();
return result;
}
@@ -1114,11 +1161,14 @@ public final class DBReader {
public List<Feed> feeds;
public int queueSize;
public int numUnreadItems;
+ public LongIntMap numUnreadFeedItems;
- public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) {
+ public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems,
+ LongIntMap numUnreadFeedItems) {
this.feeds = feeds;
this.queueSize = queueSize;
this.numUnreadItems = numUnreadItems;
+ this.numUnreadFeedItems = numUnreadFeedItems;
}
}
}
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 623ca377a..23b5391c7 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
@@ -27,6 +27,7 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
;
@@ -181,8 +182,8 @@ public class PodDBAdapter {
+ KEY_PASSWORD + " TEXT,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
+ KEY_NEXT_PAGE_LINK + " TEXT,"
- + KEY_HIDE + " TEXT)";
-
+ + KEY_HIDE + " TEXT,"
+ + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)";
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -207,7 +208,8 @@ public class PodDBAdapter {
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
- + KEY_PLAYED_DURATION + " INTEGER)";
+ + KEY_PLAYED_DURATION + " INTEGER,"
+ + KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@@ -1065,7 +1067,27 @@ public class PodDBAdapter {
Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
return c;
+ }
+ /**
+ * Returns a cursor which contains all feed items that are considered new.
+ * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
+ */
+ public final Cursor getNewItemsCursor() {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
+ Cursor c = db.rawQuery(query, null);
+ return c;
}
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
@@ -1223,13 +1245,20 @@ public class PodDBAdapter {
return result;
}
- public final int getNumberOfUnreadItems(long feedId) {
- final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
- " WHERE " + KEY_FEED + " = " + feedId + " AND " + KEY_READ + " = 0";
+ public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
+ final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") "
+ + " AND " + KEY_READ + " = 0"
+ + " GROUP BY " + KEY_FEED;
Cursor c = db.rawQuery(query, null);
- int result = 0;
+ LongIntMap result = new LongIntMap(c.getCount());
if (c.moveToFirst()) {
- result = c.getInt(0);
+ do {
+ long feedId = c.getLong(0);
+ int count = c.getInt(1);
+ result.put(feedId, count);
+ } while(c.moveToNext());
}
c.close();
return result;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
new file mode 100644
index 000000000..33fd252eb
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
@@ -0,0 +1,252 @@
+package de.danoeh.antennapod.core.util;
+
+
+/**
+ * Fast and memory efficient long to long map
+ */
+public class LongIntMap {
+
+ private long[] keys;
+ private int[] values;
+ private int size;
+
+ /**
+ * Creates a new LongLongMap containing no mappings.
+ */
+ public LongIntMap() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseLongArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public LongIntMap(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ keys = new long[initialCapacity];
+ values = new int[initialCapacity];
+ size = 0;
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == keys.length) {
+ // Resize.
+ long[] newKeysArray = new long[size * 3 / 2 + 10];
+ int[] newValuesArray = new int[size * 3 / 2 + 10];
+ System.arraycopy(keys, 0, newKeysArray, 0, size);
+ System.arraycopy(values, 0, newValuesArray, 0, size);
+ keys = newKeysArray;
+ values = newValuesArray;
+ }
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public int get(long key) {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public int get(long key, int valueIfKeyNotFound) {
+ int index = indexOfKey(key);
+ if(index >= 0) {
+ return values[index];
+ } else {
+ return valueIfKeyNotFound;
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public boolean delete(long key) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ removeAt(index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(keys, index + 1, keys, index, size - (index + 1));
+ System.arraycopy(values, index + 1, values, index, size - (index + 1));
+ size--;
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, int value) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ values[index] = value;
+ } else {
+ growIfNeeded();
+ keys[size] = key;
+ values[size] = value;
+ size++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The keys corresponding to indices in ascending order are guaranteed to
+ * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+ * smallest key and <code>keyAt(size()-1)</code> will return the largest
+ * key.</p>
+ */
+ public long keyAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return keys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The values corresponding to indices in ascending order are guaranteed
+ * to be associated with keys in ascending order, e.g.,
+ * <code>valueAt(0)</code> will return the value associated with the
+ * smallest key and <code>valueAt(size()-1)</code> will return the value
+ * associated with the largest key.</p>
+ */
+ public int valueAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[index];
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ for(int i=0; i < size; i++) {
+ if(keys[i] == key) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(long value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ keys = new long[10];
+ values = new int[10];
+ size = 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof LongIntMap)) {
+ return false;
+ }
+ LongIntMap otherMap = (LongIntMap) other;
+ if (size != otherMap.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (keys[i] != otherMap.keys[i] ||
+ values[i] != otherMap.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ long value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "LongLongMap{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size * 28);
+ buffer.append("LongLongMap{");
+ for (int i=0; i < size; i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ long key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ long value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
index f5d0cab0c..8934f3272 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -32,7 +32,6 @@ public final class LongList {
@Override
public int hashCode() {
- Arrays.hashCode(values);
int hashCode = 1;
for (int i = 0; i < size; i++) {
long value = values[i];