package de.danoeh.antennapod.core.storage;
import android.database.Cursor;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
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.feed.ID3Chapter;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
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;
import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
/**
* Provides methods for reading data from the AntennaPod database.
* In general, all database calls in DBReader-methods are executed on the caller's thread.
* This means that the caller should make sure that DBReader-methods are not executed on the GUI-thread.
* This class will use the {@link de.danoeh.antennapod.core.feed.EventDistributor} to notify listeners about changes in the database.
*/
public final class DBReader {
private static final String TAG = "DBReader";
/**
* Maximum size of the list returned by {@link #getPlaybackHistory()}.
*/
public static final int PLAYBACK_HISTORY_SIZE = 50;
/**
* Maximum size of the list returned by {@link #getDownloadLog()}.
*/
public static final int DOWNLOAD_LOG_SIZE = 200;
private DBReader() {
}
/**
* Returns a list of Feeds, sorted alphabetically by their title.
*
* @return A list of Feeds, sorted alphabetically by their title. A Feed-object
* of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
* can be loaded separately with {@link #getFeedItemList(Feed)}.
*/
public static List<Feed> getFeedList() {
Log.d(TAG, "Extracting Feedlist");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<Feed> result = getFeedList(adapter);
adapter.close();
return result;
}
private static List<Feed> getFeedList(PodDBAdapter adapter) {
Cursor feedlistCursor = adapter.getAllFeedsCursor();
List<Feed> feeds = new ArrayList<>(feedlistCursor.getCount());
if (feedlistCursor.moveToFirst()) {
do {
Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
feeds.add(feed);
} while (feedlistCursor.moveToNext());
}
feedlistCursor.close();
return feeds;
}
/**
* Returns a list with the download URLs of all feeds.
*
* @return A list of Strings with the download URLs of all feeds.
*/
public static List<String> getFeedListDownloadUrls() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
List<String> result = new ArrayList<>();
adapter.open();
Cursor feeds = adapter.getFeedCursorDownloadUrls();
if (feeds.moveToFirst()) {
do {
result.add(feeds.getString(1));
} while (feeds.moveToNext());
}
feeds.close();
adapter.close();
return result;
}
/**
* Loads additional data in to the feed items from other database queries
* @param items the FeedItems who should have other data loaded
*/
public static void loadAdditionalFeedItemListData(List<FeedItem> items) {
loadTagsOfFeedItemList(items);
loadFeedDataOfFeedItemList(items);
}
public static void loadTagsOfFeedItemList(List<FeedItem> items) {
LongList favoriteIds = getFavoriteIDList();
LongList queueIds = getQueueIDList();
for (FeedItem item : items) {
if (favoriteIds.contains(item.getId())) {
item.addTag(FeedItem.TAG_FAVORITE);
}
if (queueIds.contains(item.getId())) {
item.addTag(FeedItem.TAG_QUEUE);
}
}
}
/**
* Takes a list of FeedItems and loads their corresponding Feed-objects from the database.
* The feedID-attribute of a FeedItem must be set to the ID of its feed or the method will
* not find the correct feed of an item.
*
* @param items The FeedItems whose Feed-objects should be loaded.
*/
public static void loadFeedDataOfFeedItemList(List<FeedItem> items) {
List<Feed> feeds = getFeedList();
for (FeedItem item : items) {
for (Feed feed : feeds) {
if (feed.getId() == item.getFeedId()) {
item.setFeed(feed);
break;
}
}
if (item.getFeed() == null) {
Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId());
}
}
}
/**
* Loads the list of FeedItems for a certain Feed-object. This method should NOT be used if the FeedItems are not
* used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList()} instead.
*
* @param feed The Feed whose items should be loaded
* @return A list with the FeedItems of the Feed. The Feed-attribute of the FeedItems will already be set correctly.
* The method does NOT change the items-attribute of the feed.
*/
public static List<FeedItem> getFeedItemList(final Feed feed) {
Log.d(TAG, "getFeedItemList() called with: " + "feed = [" + feed + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
adapter.close();
Collections.sort(items, new FeedItemPubdateComparator());
for (FeedItem item : items) {
item.setFeed(feed);
}
return items;
}
public static List<FeedItem> extractItemlistFromCursor(Cursor itemlistCursor) {
Log.d(TAG, "extractItemlistFromCursor() called with: " + "itemlistCursor = [" + itemlistCursor + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
adapter.close();
return result;
}
private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter,
Cursor cursor) {
List<FeedItem> result = new ArrayList<>(cursor.getCount());
LongList imageIds = new LongList(cursor.getCount());
LongList itemIds = new LongList(cursor.getCount());
if (cursor.moveToFirst()) {
do {
int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
long imageId = cursor.getLong(indexImage);
imageIds.add(imageId);
FeedItem item = FeedItem.fromCursor(cursor);
result.add(item);
itemIds.add(item.getId());
} while (cursor.moveToNext());
Map<Long,FeedImage> images = getFeedImages(adapter, imageIds.toArray());
Map<Long,FeedMedia> medias = getFeedMedia(adapter, itemIds.toArray());
for(int i=0; i < result.size(); i++) {
FeedItem item = result.get(i);
long imageId = imageIds.get(i);
FeedImage image = images.get(imageId);
item.setImage(image);
FeedMedia media = medias.get(item.getId());
item.setMedia(media);
if(media != null) {
media.setItem(item);
}
}
}
return result;
}
private static Map<Long,FeedMedia> getFeedMedia(PodDBAdapter adapter,
long... itemIds) {
String[] ids = new String[itemIds.length];
for(int i=0, len=itemIds.length; i < len; i++) {
ids[i] = String.valueOf(itemIds[i]);
}
Map<Long,FeedMedia> result = new ArrayMap<>(itemIds.length);
Cursor cursor = adapter.getFeedMediaCursor(ids);
try {
if (cursor.moveToFirst()) {
do {
int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
long itemId = cursor.getLong(index);
FeedMedia media = FeedMedia.fromCursor(cursor);
result.put(itemId, media);
} while (cursor.moveToNext());
}
} finally {
cursor.close();
}
return result;
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
Cursor cursor) {
final FeedImage image;
int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
long imageId = cursor.getLong(indexImage);
if (imageId != 0) {
image = getFeedImage(adapter, imageId);
} else {
image = null;
}
Feed feed = Feed.fromCursor(cursor);
if (image != null) {
feed.setImage(image);
image.setOwner(feed);
}
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
return feed;
}
static List<FeedItem> getQueue(PodDBAdapter adapter) {
Log.d(TAG, "getQueue()");
Cursor itemlistCursor = adapter.getQueueCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
return items;
}
/**
* Loads the IDs of the FeedItems in the queue. This method should be preferred over
* {@link #getQueue()} if the FeedItems of the queue are not needed.
*
* @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
public static LongList getQueueIDList() {
Log.d(TAG, "getQueueIDList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
LongList result = getQueueIDList(adapter);
adapter.close();
return result;
}
static LongList getQueueIDList(PodDBAdapter adapter) {
Cursor queueCursor = adapter.getQueueIDCursor();
LongList queueIds = new LongList(queueCursor.getCount());
if (queueCursor.moveToFirst()) {
do {
queueIds.add(queueCursor.getLong(0));
} while (queueCursor.moveToNext());
}
queueCursor.close();
return queueIds;
}
/**
* Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using
* {@link #getQueueIDList()} instead.
*
* @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
public static List<FeedItem> getQueue() {
Log.d(TAG, "getQueue() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> items = getQueue(adapter);
adapter.close();
return items;
}
/**
* Loads a list of FeedItems whose episode has been downloaded.
*
* @return A list of FeedItems whose episdoe has been downloaded.
*/
public static List<FeedItem> getDownloadedItems() {
Log.d(TAG, "getDownloadedItems() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getDownloadedItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
adapter.close();
Collections.sort(items, new FeedItemPubdateComparator());
return items;
}
/**
* Loads a list of FeedItems whose 'read'-attribute is set to false.
*
* @return A list of FeedItems whose 'read'-attribute it set to false.
*/
public static List<FeedItem> getUnreadItemsList() {
Log.d(TAG, "getUnreadItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getUnreadItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
adapter.close();
return items;
}
/**
* Loads a list of FeedItems that are considered new.
* Excludes items from feeds that do not have keep updated enabled.
* @return A list of FeedItems that are considered new.
*/
public static List<FeedItem> getNewItemsList() {
Log.d(TAG, "getNewItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getNewItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
adapter.close();
return items;
}
public static List<FeedItem> getFavoriteItemsList() {
Log.d(TAG, "getFavoriteItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getFavoritesCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
adapter.close();
return items;
}
public static LongList getFavoriteIDList() {
Log.d(TAG, "getFavoriteIDList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor favoritesCursor = adapter.getFavoritesCursor();
LongList favoriteIDs = new LongList(favoritesCursor.getCount());
if (favoritesCursor.moveToFirst()) {
do {
favoriteIDs.add(favoritesCursor.getLong(0));
} while (favoritesCursor.moveToNext());
}
favoritesCursor.close();
adapter.close();
return favoriteIDs;
}
/**
* Loads a list of FeedItems sorted by pubDate in descending order.
*
* @param limit The maximum number of episodes that should be loaded.
*/
public static List<FeedItem> getRecentlyPublishedEpisodes(int limit) {
Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "limit = [" + limit + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getRecentlyPublishedItemsCursor(limit);
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
adapter.close();
return items;
}
/**
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
* has been completed at least once.
*
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
*/
public static List<FeedItem> getPlaybackHistory() {
Log.d(TAG, "getPlaybackHistory() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
String[] itemIds = new String[mediaCursor.getCount()];
for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
int index = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
itemIds[i] = Long.toString(mediaCursor.getLong(index));
}
mediaCursor.close();
Cursor itemCursor = adapter.getFeedItemCursor(itemIds);
List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
loadAdditionalFeedItemListData(items);
itemCursor.close();
adapter.close();
Collections.sort(items, new PlaybackCompletionDateComparator());
return items;
}
/**
* Loads the download log from the database.
*
* @return A list with DownloadStatus objects that represent the download log.
* The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
*/
public static List<DownloadStatus> getDownloadLog() {
Log.d(TAG, "getDownloadLog() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE);
List<DownloadStatus> downloadLog = new ArrayList<>(logCursor.getCount());
if (logCursor.moveToFirst()) {
do {
DownloadStatus status = DownloadStatus.fromCursor(logCursor);
downloadLog.add(status);
} while (logCursor.moveToNext());
}
logCursor.close();
adapter.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
/**
* Loads the download log for a particular feed from the database.
*
* @param feed Feed for which the download log is loaded
* @return A list with DownloadStatus objects that represent the feed's download log,
* newest events first.
*/
public static List<DownloadStatus> getFeedDownloadLog(Feed feed) {
Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feed + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId());
List<DownloadStatus> downloadLog = new ArrayList<>(cursor.getCount());
if (cursor.moveToFirst()) {
do {
DownloadStatus status = DownloadStatus.fromCursor(cursor);
downloadLog.add(status);
} while (cursor.moveToNext());
}
cursor.close();
adapter.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
/**
* Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
* {@link #getFeedItemList(Feed)} if only metadata about
* the FeedItems is needed.
*
* @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title.
*/
public static List<FeedItemStatistics> getFeedStatisticsList() {
Log.d(TAG, "getFeedStatisticsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItemStatistics> result = new ArrayList<>();
Cursor cursor = adapter.getFeedStatisticsCursor();
if (cursor.moveToFirst()) {
do {
FeedItemStatistics fis = FeedItemStatistics.fromCursor(cursor);
result.add(fis);
} while (cursor.moveToNext());
}
cursor.close();
adapter.close();
return result;
}
/**
* Loads a specific Feed from the database.
*
* @param feedId The ID of the Feed
* @return The Feed or null if the Feed could not be found. The Feeds FeedItems will also be loaded from the
* database and the items-attribute will be set correctly.
*/
public static Feed getFeed(final long feedId) {
Log.d(TAG, "getFeed() called with: " + "feedId = [" + feedId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Feed result = getFeed(feedId, adapter);
adapter.close();
return result;
}
static Feed getFeed(final long feedId, PodDBAdapter adapter) {
Feed feed = null;
Cursor feedCursor = adapter.getFeedCursor(feedId);
if (feedCursor.moveToFirst()) {
feed = extractFeedFromCursorRow(adapter, feedCursor);
feed.setItems(getFeedItemList(feed));
} else {
Log.e(TAG, "getFeed could not find feed with id " + feedId);
}
feedCursor.close();
return feed;
}
static FeedItem getFeedItem(final long itemId, PodDBAdapter adapter) {
Log.d(TAG, "Loading feeditem with id " + itemId);
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
if (!itemCursor.moveToFirst()) {
itemCursor.close();
return null;
}
List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
itemCursor.close();
if (list.size() > 0) {
item = list.get(0);
loadAdditionalFeedItemListData(list);
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
}
}
return item;
}
static List<FeedItem> getFeedItems(PodDBAdapter adapter, final long... itemIds) {
String[] ids = new String[itemIds.length];
for(int i = 0; i < itemIds.length; i++) {
long itemId = itemIds[i];
ids[i] = Long.toString(itemId);
}
List<FeedItem> result;
Cursor itemCursor = adapter.getFeedItemCursor(ids);
if (itemCursor.moveToFirst()) {
result = extractItemlistFromCursor(adapter, itemCursor);
loadAdditionalFeedItemListData(result);
for(FeedItem item : result) {
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
}
}
} else {
result = Collections.emptyList();
}
itemCursor.close();
return result;
}
/**
* Loads a specific FeedItem from the database. This method should not be used for loading more
* than one FeedItem because this method might query the database several times for each item.
*
* @param itemId The ID of the FeedItem
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
public static FeedItem getFeedItem(final long itemId) {
Log.d(TAG, "getFeedItem() called with: " + "itemId = [" + itemId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
FeedItem item = getFeedItem(itemId, adapter);
adapter.close();
return item;
}
static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(podcastUrl, episodeUrl);
if (itemCursor.moveToFirst()) {
List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
if (list.size() > 0) {
item = list.get(0);
loadAdditionalFeedItemListData(list);
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
}
}
}
itemCursor.close();
return item;
}
/**
* Loads specific FeedItems from the database. This method canbe used for loading more
* than one FeedItem
*
* @param itemIds The IDs of the FeedItems
* @return The FeedItems or an empty list if none of the FeedItems could be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItems will also be loaded from the database.
*/
public static List<FeedItem> getFeedItems(final long... itemIds) {
Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + Arrays.toString(itemIds) + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> items = getFeedItems(adapter, itemIds);
adapter.close();
return items;
}
/**
* Returns credentials based on image URL
*
* @param imageUrl The URL of the image
* @return Credentials in format "<Username>:<Password>", empty String if no authorization given
*/
public static String getImageAuthentication(final String imageUrl) {
Log.d(TAG, "getImageAuthentication() called with: " + "imageUrl = [" + imageUrl + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
String credentials = getImageAuthentication(imageUrl, adapter);
adapter.close();
return credentials;
}
static String getImageAuthentication(final String imageUrl, PodDBAdapter adapter) {
String credentials = null;
Cursor cursor = adapter.getImageAuthenticationCursor(imageUrl);
try {
if (cursor.moveToFirst()) {
String username = cursor.getString(0);
String password = cursor.getString(1);
if(username != null && password != null) {
credentials = username + ":" + password;
} else {
credentials = "";
}
} else {
credentials = "";
}
} finally {
cursor.close();
}
return credentials;
}
/**
* Loads a specific FeedItem from the database.
*
* @param podcastUrl the corresponding feed's url
* @param episodeUrl the feed item's url
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
public static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl) {
Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
FeedItem item = getFeedItem(podcastUrl, episodeUrl, adapter);
adapter.close();
return item;
}
/**
* Loads additional information about a FeedItem, e.g. shownotes
*
* @param item The FeedItem
*/
public static void loadExtraInformationOfFeedItem(final FeedItem item) {
Log.d(TAG, "loadExtraInformationOfFeedItem() called with: " + "item = [" + item + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor extraCursor = adapter.getExtraInformationOfItem(item);
if (extraCursor.moveToFirst()) {
int indexDescription = extraCursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION);
String description = extraCursor.getString(indexDescription);
int indexContentEncoded = extraCursor.getColumnIndex(PodDBAdapter.KEY_CONTENT_ENCODED);
String contentEncoded = extraCursor.getString(indexContentEncoded);
item.setDescription(description);
item.setContentEncoded(contentEncoded);
}
extraCursor.close();
adapter.close();
}
/**
* Loads the list of chapters that belongs to this FeedItem if available. This method overwrites
* any chapters that this FeedItem has. If no chapters were found in the database, the chapters
* reference of the FeedItem will be set to null.
*
* @param item The FeedItem
*/
public static void loadChaptersOfFeedItem(final FeedItem item) {
Log.d(TAG, "loadChaptersOfFeedItem() called with: " + "item = [" + item + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
loadChaptersOfFeedItem(adapter, item);
adapter.close();
}
static void loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
Cursor chapterCursor = adapter.getSimpleChaptersOfFeedItemCursor(item);
if (chapterCursor.moveToFirst()) {
item.setChapters(new ArrayList<>());
do {
int indexType = chapterCursor.getColumnIndex(PodDBAdapter.KEY_CHAPTER_TYPE);
int indexStart = chapterCursor.getColumnIndex(PodDBAdapter.KEY_START);
int indexTitle = chapterCursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
int indexLink = chapterCursor.getColumnIndex(PodDBAdapter.KEY_LINK);
int chapterType = chapterCursor.getInt(indexType);
Chapter chapter = null;
long start = chapterCursor.getLong(indexStart);
String title = chapterCursor.getString(indexTitle);
String link = chapterCursor.getString(indexLink);
switch (chapterType) {
case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
chapter = new SimpleChapter(start, title, item,
link);
break;
case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
chapter = new ID3Chapter(start, title, item,
link);
break;
case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
chapter = new VorbisCommentChapter(start,
title, item, link);
break;
}
if (chapter != null) {
int indexId = chapterCursor.getColumnIndex(PodDBAdapter.KEY_ID);
chapter.setId(chapterCursor.getLong(indexId));
item.getChapters().add(chapter);
}
} while (chapterCursor.moveToNext());
} else {
item.setChapters(null);
}
chapterCursor.close();
}
/**
* Returns the number of downloaded episodes.
*
* @return The number of downloaded episodes.
*/
public static int getNumberOfDownloadedEpisodes() {
Log.d(TAG, "getNumberOfDownloadedEpisodes() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final int result = adapter.getNumberOfDownloadedEpisodes();
adapter.close();
return result;
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageId The id of the object
* @return The found object
*/
public static FeedImage getFeedImage(final long imageId) {
Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
FeedImage result = getFeedImage(adapter, imageId);
adapter.close();
return result;
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageId The id of the object
* @return The found object
*/
private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) {
return getFeedImages(adapter, imageId).get(imageId);
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageIds The ids of the images
* @return Map that associates the id of an image with the image itself
*/
private static Map<Long,FeedImage> getFeedImages(PodDBAdapter adapter, final long... imageIds) {
String[] ids = new String[imageIds.length];
for(int i=0, len=imageIds.length; i < len; i++) {
ids[i] = String.valueOf(imageIds[i]);
}
Cursor cursor = adapter.getImageCursor(ids);
Map<Long, FeedImage> result = new ArrayMap<>(cursor.getCount());
try {
if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
return Collections.emptyMap();
}
do {
FeedImage image = FeedImage.fromCursor(cursor);
result.put(image.getId(), image);
} while(cursor.moveToNext());
} finally {
cursor.close();
}
return result;
}
/**
* Searches the DB for a FeedMedia of the given id.
*
* @param mediaId The id of the object
* @return The found object
*/
public static FeedMedia getFeedMedia(final long mediaId) {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId);
if (!mediaCursor.moveToFirst()) {
mediaCursor.close();
return null;
}
int indexFeedItem = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
long itemId = mediaCursor.getLong(indexFeedItem);
FeedMedia media = FeedMedia.fromCursor(mediaCursor);
mediaCursor.close();
if(media != null) {
FeedItem item = getFeedItem(itemId);
if (item != null) {
media.setItem(item);
item.setMedia(media);
}
}
adapter.close();
return media;
}
/**
* Searches the DB for statistics
*
* @return The StatisticsInfo object
*/
public static StatisticsData getStatistics() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
long totalTime = 0;
List<StatisticsItem> feedTime = new ArrayList<>();
List<Feed> feeds = getFeedList();
for (Feed feed : feeds) {
long feedPlayedTime = 0;
long feedTotalTime = 0;
long episodes = 0;
long episodesStarted = 0;
List<FeedItem> items = getFeed(feed.getId()).getItems();
for(FeedItem item : items) {
FeedMedia media = item.getMedia();
if(media == null) {
continue;
}
feedPlayedTime += media.getPlayedDuration() / 1000;
if(media.getPlayedDuration() > 0) {
episodesStarted++;
}
feedTotalTime += media.getDuration() / 1000;
episodes++;
}
feedTime.add(new StatisticsItem(
feed, feedTotalTime, feedPlayedTime, episodes, episodesStarted));
totalTime += feedPlayedTime;
}
Collections.sort(feedTime, (item1, item2) -> {
if(item1.timePlayed > item2.timePlayed) {
return -1;
} else if(item1.timePlayed < item2.timePlayed) {
return 1;
} else {
return 0;
}
});
adapter.close();
return new StatisticsData(totalTime, feedTime);
}
public static class StatisticsData {
public long totalTime;
public List<StatisticsItem> feedTime;
public StatisticsData(long totalTime, List<StatisticsItem> feedTime) {
this.totalTime = totalTime;
this.feedTime = feedTime;
}
}
public static class StatisticsItem {
public Feed feed;
public long time;
public long timePlayed;
public long episodes;
public long episodesStarted;
public StatisticsItem(Feed feed, long time, long timePlayed,
long episodes, long episodesStarted) {
this.feed = feed;
this.time = time;
this.timePlayed = timePlayed;
this.episodes = episodes;
this.episodesStarted = episodesStarted;
}
}
/**
* Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems.
*
* @return The flattr queue as a List.
*/
public static List<FlattrThing> getFlattrQueue() {
Log.d(TAG, "getFlattrQueue() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FlattrThing> result = new ArrayList<>();
// load feeds
Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor();
if (feedCursor.moveToFirst()) {
do {
result.add(extractFeedFromCursorRow(adapter, feedCursor));
} while (feedCursor.moveToNext());
}
feedCursor.close();
//load feed items
Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor();
result.addAll(extractItemlistFromCursor(adapter, feedItemCursor));
feedItemCursor.close();
adapter.close();
Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items.");
return result;
}
/**
* Returns data necessary for displaying the navigation drawer. This includes
* the list of subscriptions, the number of items in the queue and the number of unread
* items.
*
*/
public static NavDrawerData getNavDrawerData() {
Log.d(TAG, "getNavDrawerData() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
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 feedCounters = adapter.getFeedCounters(feedIds);
Comparator<Feed> comparator;
int feedOrder = UserPreferences.getFeedOrder();
if(feedOrder == UserPreferences.FEED_ORDER_COUNTER) {
comparator = (lhs, rhs) -> {
long counterLhs = feedCounters.get(lhs.getId());
long counterRhs = feedCounters.get(rhs.getId());
if(counterLhs > counterRhs) {
// reverse natural order: podcast with most unplayed episodes first
return -1;
} else if(counterLhs == counterRhs) {
return lhs.getTitle().compareTo(rhs.getTitle());
} else {
return 1;
}
};
} else if(feedOrder == UserPreferences.FEED_ORDER_ALPHABETICAL) {
comparator = (lhs, rhs) -> {
String t1 = lhs.getTitle();
String t2 = rhs.getTitle();
if(t1 == null) {
return 1;
} else if(t2 == null) {
return -1;
} else {
return t1.toLowerCase().compareTo(t2.toLowerCase());
}
};
} else {
comparator = (lhs, rhs) -> {
if(lhs.getItems() == null || lhs.getItems().size() == 0) {
List<FeedItem> items = DBReader.getFeedItemList(lhs);
lhs.setItems(items);
}
if(rhs.getItems() == null || rhs.getItems().size() == 0) {
List<FeedItem> items = DBReader.getFeedItemList(rhs);
rhs.setItems(items);
}
if(lhs.getMostRecentItem() == null) {
return 1;
} else if(rhs.getMostRecentItem() == null) {
return -1;
} else {
Date d1 = lhs.getMostRecentItem().getPubDate();
Date d2 = rhs.getMostRecentItem().getPubDate();
return d2.compareTo(d1);
}
};
}
Collections.sort(feeds, comparator);
int queueSize = adapter.getQueueSize();
int numNewItems = adapter.getNumberOfNewItems();
int numDownloadedItems = adapter.getNumberOfDownloadedEpisodes();
NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numDownloadedItems,
feedCounters, UserPreferences.getEpisodeCleanupAlgorithm().getReclaimableItems());
adapter.close();
return result;
}
public static class NavDrawerData {
public List<Feed> feeds;
public int queueSize;
public int numNewItems;
public int numDownloadedItems;
public LongIntMap feedCounters;
public int reclaimableSpace;
public NavDrawerData(List<Feed> feeds,
int queueSize,
int numNewItems,
int numDownloadedItems,
LongIntMap feedIndicatorValues,
int reclaimableSpace) {
this.feeds = feeds;
this.queueSize = queueSize;
this.numNewItems = numNewItems;
this.numDownloadedItems = numDownloadedItems;
this.feedCounters = feedIndicatorValues;
this.reclaimableSpace = reclaimableSpace;
}
}
}