Order feeds by number of unread items (descending)
This commit is contained in:
parent
406dab0a24
commit
6f5d23c557
|
@ -706,5 +706,10 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
|
||||||
public int getNumberOfUnreadItems() {
|
public int getNumberOfUnreadItems() {
|
||||||
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
|
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumberOfUnreadFeedItems(long feedId) {
|
||||||
|
return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,6 +511,11 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
|
||||||
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
|
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumberOfUnreadFeedItems(long feedId) {
|
||||||
|
return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.core.feed.Feed;
|
import de.danoeh.antennapod.core.feed.Feed;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.storage.DBReader;
|
|
||||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||||
import de.danoeh.antennapod.fragment.DownloadsFragment;
|
import de.danoeh.antennapod.fragment.DownloadsFragment;
|
||||||
|
@ -266,12 +265,13 @@ public class NavListAdapter extends BaseAdapter
|
||||||
|
|
||||||
holder.title.setText(feed.getTitle());
|
holder.title.setText(feed.getTitle());
|
||||||
|
|
||||||
int feedUnreadItems = DBReader.getNumberOfUnreadItems(context, feed.getId());
|
|
||||||
if(feed.hasLastUpdateFailed()) {
|
if(feed.hasLastUpdateFailed()) {
|
||||||
holder.failure.setVisibility(View.VISIBLE);
|
holder.failure.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
holder.failure.setVisibility(View.GONE);
|
holder.failure.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
int feedUnreadItems = itemAccess.getNumberOfUnreadFeedItems(feed.getId());
|
||||||
if(feedUnreadItems > 0) {
|
if(feedUnreadItems > 0) {
|
||||||
holder.count.setVisibility(View.VISIBLE);
|
holder.count.setVisibility(View.VISIBLE);
|
||||||
holder.count.setText(String.valueOf(feedUnreadItems));
|
holder.count.setText(String.valueOf(feedUnreadItems));
|
||||||
|
@ -301,6 +301,7 @@ public class NavListAdapter extends BaseAdapter
|
||||||
int getSelectedItemIndex();
|
int getSelectedItemIndex();
|
||||||
int getQueueSize();
|
int getQueueSize();
|
||||||
int getNumberOfUnreadItems();
|
int getNumberOfUnreadItems();
|
||||||
|
int getNumberOfUnreadFeedItems(long feedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,7 @@ import de.danoeh.antennapod.core.storage.DBTasks;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||||
import de.danoeh.antennapod.core.util.LongList;
|
import de.danoeh.antennapod.core.util.LongList;
|
||||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
|
||||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||||
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows unread or recently published episodes
|
* Shows unread or recently published episodes
|
||||||
|
@ -70,14 +68,13 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
private TextView txtvEmpty;
|
private TextView txtvEmpty;
|
||||||
private ProgressBar progLoading;
|
private ProgressBar progLoading;
|
||||||
|
|
||||||
private List<FeedItem> unreadItems;
|
private List<FeedItem> episodes;
|
||||||
private List<FeedItem> recentItems;
|
|
||||||
private LongList queueAccess;
|
private LongList queueAccess;
|
||||||
private List<Downloader> downloaderList;
|
private List<Downloader> downloaderList;
|
||||||
|
|
||||||
private boolean itemsLoaded = false;
|
private boolean itemsLoaded = false;
|
||||||
private boolean viewsCreated = false;
|
private boolean viewsCreated = false;
|
||||||
private boolean showOnlyNewEpisodes = false;
|
private final boolean showOnlyNewEpisodes;
|
||||||
|
|
||||||
private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>();
|
private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>();
|
||||||
|
|
||||||
|
@ -225,7 +222,7 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
if (itemsLoaded) {
|
if (itemsLoaded) {
|
||||||
MenuItem menuItem = menu.findItem(R.id.mark_all_read_item);
|
MenuItem menuItem = menu.findItem(R.id.mark_all_read_item);
|
||||||
if (menuItem != null) {
|
if (menuItem != null) {
|
||||||
menuItem.setVisible(unreadItems != null && !unreadItems.isEmpty());
|
menuItem.setVisible(episodes != null && !episodes.isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,7 +342,7 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
if (itemsLoaded) {
|
if (itemsLoaded) {
|
||||||
return (showOnlyNewEpisodes) ? unreadItems.size() : recentItems.size();
|
return episodes.size();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -353,7 +350,7 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public FeedItem getItem(int position) {
|
public FeedItem getItem(int position) {
|
||||||
if (itemsLoaded) {
|
if (itemsLoaded) {
|
||||||
return (showOnlyNewEpisodes) ? unreadItems.get(position) : recentItems.get(position);
|
return episodes.get(position);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -436,10 +433,17 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
protected Object[] doInBackground(Void... params) {
|
protected Object[] doInBackground(Void... params) {
|
||||||
Context context = activity.get();
|
Context context = activity.get();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
|
if(showOnlyNewEpisodes) {
|
||||||
|
return new Object[] {
|
||||||
|
DBReader.getNewItemsList(context),
|
||||||
|
DBReader.getQueueIDList(context)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return new Object[]{
|
return new Object[]{
|
||||||
DBReader.getUnreadItemsList(context),
|
|
||||||
DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
|
DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
|
||||||
DBReader.getQueueIDList(context)};
|
DBReader.getQueueIDList(context)
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -452,9 +456,8 @@ public class AllEpisodesFragment extends Fragment {
|
||||||
progLoading.setVisibility(View.GONE);
|
progLoading.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (lists != null) {
|
if (lists != null) {
|
||||||
unreadItems = (List<FeedItem>) lists[0];
|
episodes = (List<FeedItem>) lists[0];
|
||||||
recentItems = (List<FeedItem>) lists[1];
|
queueAccess = (LongList) lists[1];
|
||||||
queueAccess = (LongList) lists[2];
|
|
||||||
itemsLoaded = true;
|
itemsLoaded = true;
|
||||||
if (viewsCreated && activity.get() != null) {
|
if (viewsCreated && activity.get() != null) {
|
||||||
onFragmentLoaded();
|
onFragmentLoaded();
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
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.feed.VorbisCommentChapter;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
import de.danoeh.antennapod.core.util.DownloadError;
|
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.LongList;
|
||||||
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
|
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
|
||||||
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
||||||
|
@ -489,6 +491,29 @@ public final class DBReader {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
* @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);
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
||||||
adapter.open();
|
adapter.open();
|
||||||
final int result = adapter.getNumberOfUnreadItems(feedId);
|
final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
|
||||||
adapter.close();
|
adapter.close();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1103,9 +1128,31 @@ public final class DBReader {
|
||||||
PodDBAdapter adapter = new PodDBAdapter(context);
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
||||||
adapter.open();
|
adapter.open();
|
||||||
List<Feed> feeds = getFeedList(adapter);
|
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 queueSize = adapter.getQueueSize();
|
||||||
int numUnreadItems = adapter.getNumberOfUnreadItems();
|
int numUnreadItems = adapter.getNumberOfUnreadItems();
|
||||||
NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
|
NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems, numUnreadFeedItems);
|
||||||
adapter.close();
|
adapter.close();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1114,11 +1161,14 @@ public final class DBReader {
|
||||||
public List<Feed> feeds;
|
public List<Feed> feeds;
|
||||||
public int queueSize;
|
public int queueSize;
|
||||||
public int numUnreadItems;
|
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.feeds = feeds;
|
||||||
this.queueSize = queueSize;
|
this.queueSize = queueSize;
|
||||||
this.numUnreadItems = numUnreadItems;
|
this.numUnreadItems = numUnreadItems;
|
||||||
|
this.numUnreadFeedItems = numUnreadFeedItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
|
import de.danoeh.antennapod.core.util.LongIntMap;
|
||||||
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
|
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
|
||||||
|
|
||||||
;
|
;
|
||||||
|
@ -181,8 +182,8 @@ public class PodDBAdapter {
|
||||||
+ KEY_PASSWORD + " TEXT,"
|
+ KEY_PASSWORD + " TEXT,"
|
||||||
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
|
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
|
||||||
+ KEY_NEXT_PAGE_LINK + " TEXT,"
|
+ 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 "
|
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
|
||||||
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
|
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
|
||||||
|
@ -207,7 +208,8 @@ public class PodDBAdapter {
|
||||||
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
|
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
|
||||||
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
|
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
|
||||||
+ KEY_FEEDITEM + " 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 "
|
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
|
||||||
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
|
+ 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},
|
Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
|
||||||
KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
|
KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
|
||||||
return c;
|
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) {
|
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
|
||||||
|
@ -1223,13 +1245,20 @@ public class PodDBAdapter {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getNumberOfUnreadItems(long feedId) {
|
public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
|
||||||
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
|
final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
|
||||||
" WHERE " + KEY_FEED + " = " + feedId + " AND " + KEY_READ + " = 0";
|
+ " 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);
|
Cursor c = db.rawQuery(query, null);
|
||||||
int result = 0;
|
LongIntMap result = new LongIntMap(c.getCount());
|
||||||
if (c.moveToFirst()) {
|
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();
|
c.close();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,6 @@ public final class LongList {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
Arrays.hashCode(values);
|
|
||||||
int hashCode = 1;
|
int hashCode = 1;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
long value = values[i];
|
long value = values[i];
|
||||||
|
|
Loading…
Reference in New Issue