From f891514ec721c6c20c135b552941937985fce951 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 27 Apr 2013 21:18:59 +0200 Subject: Created DB* classes --- src/de/danoeh/antennapod/storage/DBReader.java | 56 +++++++++++++++++++ src/de/danoeh/antennapod/storage/DBTasks.java | 77 ++++++++++++++++++++++++++ src/de/danoeh/antennapod/storage/DBWriter.java | 63 +++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 src/de/danoeh/antennapod/storage/DBReader.java create mode 100644 src/de/danoeh/antennapod/storage/DBTasks.java create mode 100644 src/de/danoeh/antennapod/storage/DBWriter.java (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java new file mode 100644 index 000000000..12502923e --- /dev/null +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -0,0 +1,56 @@ +package de.danoeh.antennapod.storage; + +import java.util.List; + +import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; + +public final class DBReader { + private static final String TAG = "DBReader"; + + private DBReader() { + } + + public static List getFeedList() { + return null; + } + + public static List getFeedItemList(long feedId) { + return null; + } + + public static List getQueue() { + return null; + } + + public static List getUnreadItemsList() { + return null; + } + + public static List getPlaybackHistory() { + return null; + } + + public static List getDownloadLog() { + return null; + } + + public static Feed getFeed(long feedId) { + return null; + } + + public FeedItem getFeedItem(long itemId) { + return null; + } + + public FeedMedia getFeedMedia(long mediaId) { + return null; + } + + public static FeedItem getFirstQueueItem() { + return null; + } + +} diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java new file mode 100644 index 000000000..75477d463 --- /dev/null +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -0,0 +1,77 @@ +package de.danoeh.antennapod.storage; + +import android.content.Context; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; + +public final class DBTasks { + private static final String TAG = "DBTasks"; + + private DBTasks() { + } + + public static void playMedia(final Context context, final long mediaId, + boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { + + } + + public static void markItemRead(final Context context, final long itemId, + final boolean read, boolean resetMediaPosition) { + } + + public static void markFeedRead(final Context context, final long feedId) { + + } + + public static void markAllItemsRead(final Context context) { + } + + public static void refreshAllFeeds(final Context context) { + } + + public void refreshExpiredFeeds(final Context context) { + } + + public static void notifyInvalidImageFile(final Context context, + final long imageId) { + } + + public static void notifyMissingFeedMediaFile(final Context context, + final long mediaId) { + } + + public static void downloadAllItemsInQueue(final Context context) {} + + public static void refreshFeed(final Context context, final long feedId) { + } + + public static void downloadFeedItem(final Context context, long... itemIds) {} + + static void downloadFeedItem(boolean performAutoCleanup, + final Context context, final long... itemIds) + throws DownloadRequestException {} + + public static void autodownloadUndownloadedItems(Context context) { + } + + private static int getPerformAutoCleanupArgs(final int episodeNumber) { + return 0; + } + + public static void performAutoCleanup(final Context context) {} + + private static int performAutoCleanup(final Context context, final int episodeNumber) { + return 0; + } + + public static void enqueueAllNewItems(final Context context) {} + + public static FeedItem getQueueSuccessorOfItem(final long itemId) { + return null; + } + + public static Feed updateFeed(final Context context, final long feedId) { + return null; + } + +} diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java new file mode 100644 index 000000000..76a1ad98d --- /dev/null +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -0,0 +1,63 @@ +package de.danoeh.antennapod.storage; + +import android.content.Context; + +public class DBWriter { + private static final String TAG = "DBWriter"; + + private DBWriter() { + } + + public static boolean deleteFeedMedia(final Context context, + final long mediaId) { + return false; + } + + public static void deleteFeed(final Context context, final long feedId) { + + } + + public static void clearPlaybackHistory(final Context context) { + + } + + public static void addItemToPlaybackHistory(final Context context, + long itemId) { + + } + + private static void removeItemFromPlaybackHistory(final Context context, + long itemId) { + + } + + public static void addDownloadStatus(final Context context, + final long statusId) { + + } + + public static void addQueueItemAt(final Context context, final long itemId, + final int index, final boolean performAutoDownload) { + + } + + public static void addQueueItem(final Context context, + final long... itemIds) { + } + + public static void clearQueue(final Context context) { + } + + public static void removeQueueItem(final Context context, + final long itemId, final boolean performAutoDownload) { + + } + + public void moveQueueItem(final Context context, int from, int to, + boolean broadcastUpdate) { + } + + void addNewFeed(final Context context, final long feedId) { + + } +} -- cgit v1.2.3 From beda074e5f7622455687949d17fc73121432e324 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Wed, 1 May 2013 11:56:46 +0200 Subject: Added DBReader-methods --- src/de/danoeh/antennapod/storage/DBReader.java | 373 +++++++++++++++++++-- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 74 ++-- 2 files changed, 407 insertions(+), 40 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 12502923e..3550aa6a2 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -1,56 +1,391 @@ package de.danoeh.antennapod.storage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; import java.util.List; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.ID3Chapter; +import de.danoeh.antennapod.feed.SimpleChapter; +import de.danoeh.antennapod.feed.VorbisCommentChapter; +import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; +import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; public final class DBReader { private static final String TAG = "DBReader"; - + private DBReader() { } - public static List getFeedList() { - return null; + public static List getFeedList(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting Feedlist"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor feedlistCursor = adapter.getAllFeedsCursor(); + List feeds = new ArrayList(feedlistCursor.getCount()); + + if (feedlistCursor.moveToFirst()) { + do { + Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); + feeds.add(feed); + } while (feedlistCursor.moveToNext()); + } + feedlistCursor.close(); + return feeds; } - public static List getFeedItemList(long feedId) { - return null; + public static void loadFeedDataOfFeedItemlist(Context context, + List items) { + List feeds = getFeedList(context); + for (FeedItem item : items) { + for (Feed feed : feeds) { + if (feed.getId() == item.getFeedId()) { + item.setFeed(feed); + break; + } + } + } } - public static List getQueue() { - return null; + public static List getFeedItemList(Context context, + final Feed feed) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + + Collections.sort(items, new FeedItemPubdateComparator()); + + adapter.close(); + + for (FeedItem item : items) { + item.setFeed(feed); + } + + return items; } - public static List getUnreadItemsList() { - return null; + private static List extractItemlistFromCursor( + PodDBAdapter adapter, Cursor itemlistCursor) { + ArrayList mediaIds = new ArrayList(); + List items = new ArrayList( + itemlistCursor.getCount()); + + if (itemlistCursor.moveToFirst()) { + do { + FeedItem item = new FeedItem(); + + item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID)); + item.setTitle(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_TITLE)); + item.setLink(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_LINK)); + item.setPubDate(new Date(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE))); + item.setPaymentLink(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK)); + item.setFeedId(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_FEED)); + long mediaId = itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_MEDIA); + if (mediaId != 0) { + mediaIds.add(String.valueOf(mediaId)); + item.setMedia(new FeedMedia(mediaId, item)); + } + item.setRead((itemlistCursor + .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true + : false); + item.setItemIdentifier(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); + + // extract chapters + boolean hasSimpleChapters = itemlistCursor + .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0; + if (hasSimpleChapters) { + Cursor chapterCursor = adapter + .getSimpleChaptersOfFeedItemCursor(item); + if (chapterCursor.moveToFirst()) { + item.setChapters(new ArrayList()); + do { + int chapterType = chapterCursor + .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); + Chapter chapter = null; + long start = chapterCursor + .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); + String title = chapterCursor + .getString(PodDBAdapter.KEY_TITLE_INDEX); + String link = chapterCursor + .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); + + 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; + } + chapter.setId(chapterCursor + .getLong(PodDBAdapter.KEY_ID_INDEX)); + item.getChapters().add(chapter); + } while (chapterCursor.moveToNext()); + } + chapterCursor.close(); + } + items.add(item); + } while (itemlistCursor.moveToNext()); + } + + extractMediafromItemlist(adapter, items, mediaIds); + Collections.sort(items, new FeedItemPubdateComparator()); + return items; } - public static List getPlaybackHistory() { - return null; + private static void extractMediafromItemlist(PodDBAdapter adapter, + List items, ArrayList mediaIds) { + + List itemsCopy = new ArrayList(items); + Cursor cursor = adapter.getFeedMediaCursor(mediaIds + .toArray(new String[mediaIds.size()])); + if (cursor.moveToFirst()) { + do { + long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + // find matching feed item + FeedItem item = getMatchingItemForMedia(mediaId, itemsCopy); + itemsCopy.remove(item); + if (item != null) { + Date playbackCompletionDate = null; + long playbackCompletionTime = cursor + .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); + if (playbackCompletionTime > 0) { + playbackCompletionDate = new Date( + playbackCompletionTime); + } + + item.setMedia(new FeedMedia( + mediaId, + item, + cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), + cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), + cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), + cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), + cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), + cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), + cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, + playbackCompletionDate)); + + } + } while (cursor.moveToNext()); + cursor.close(); + } } - public static List getDownloadLog() { - return null; + private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, + Cursor cursor) { + Date lastUpdate = new Date( + cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX)); + Feed feed = new Feed(lastUpdate); + + feed.setId(cursor.getLong(PodDBAdapter.KEY_ID_INDEX)); + feed.setTitle(cursor.getString(PodDBAdapter.KEY_TITLE_INDEX)); + feed.setLink(cursor.getString(PodDBAdapter.KEY_LINK_INDEX)); + feed.setDescription(cursor + .getString(PodDBAdapter.KEY_DESCRIPTION_INDEX)); + feed.setPaymentLink(cursor + .getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX)); + feed.setAuthor(cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX)); + feed.setLanguage(cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX)); + feed.setType(cursor.getString(PodDBAdapter.KEY_TYPE_INDEX)); + feed.setFeedIdentifier(cursor + .getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX)); + long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX); + if (imageIndex != 0) { + feed.setImage(getFeedImage(adapter, imageIndex)); + feed.getImage().setFeed(feed); + } + feed.setFile_url(cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX)); + feed.setDownload_url(cursor + .getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX)); + feed.setDownloaded(cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0); + + return feed; } - public static Feed getFeed(long feedId) { + private static FeedItem getMatchingItemForMedia(long mediaId, + List items) { + for (FeedItem item : items) { + if (item.getMedia() != null && item.getMedia().getId() == mediaId) { + return item; + } + } return null; } - public FeedItem getFeedItem(long itemId) { - return null; + public static List getQueue(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting queue"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getQueueCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + + loadFeedDataOfFeedItemlist(context, items); + + adapter.close(); + + Collections.sort(items, new FeedItemPubdateComparator()); + + return items; } - public FeedMedia getFeedMedia(long mediaId) { - return null; + public static List getUnreadItemsList(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting unread items list"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getUnreadItemsCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + + loadFeedDataOfFeedItemlist(context, items); + + adapter.close(); + + return items; } - public static FeedItem getFirstQueueItem() { + public static List getPlaybackHistory() { return null; } + public static List getDownloadLog(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting DownloadLog"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor logCursor = adapter.getDownloadLogCursor(); + List downloadLog = new ArrayList( + logCursor.getCount()); + + if (logCursor.moveToFirst()) { + do { + long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + + long feedfileId = logCursor + .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); + int feedfileType = logCursor + .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); + boolean successful = logCursor + .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; + int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX); + String reasonDetailed = logCursor + .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); + String title = logCursor + .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); + Date completionDate = new Date( + logCursor + .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); + downloadLog.add(new DownloadStatus(id, title, feedfileId, + feedfileType, successful, reason, completionDate, + reasonDetailed)); + + } while (logCursor.moveToNext()); + } + logCursor.close(); + Collections.sort(downloadLog, new DownloadStatusComparator()); + return downloadLog; + } + + public static Feed getFeed(final Context context, final long feedId) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feed with id " + feedId); + Feed feed = null; + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor feedCursor = adapter.getFeedCursor(feedId); + if (feedCursor.moveToFirst()) { + feed = extractFeedFromCursorRow(adapter, feedCursor); + feed.setItems(getFeedItemList(context, feed)); + } + adapter.close(); + return feed; + } + + public FeedItem getFeedItem(final Context context, final long itemId) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feeditem with id " + itemId); + FeedItem item = null; + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor itemCursor = adapter.getFeedItemCursor(itemId); + if (itemCursor.moveToFirst()) { + List list = extractItemlistFromCursor(adapter, itemCursor); + if (list.size() > 0) { + item = list.get(0); + } + } + adapter.close(); + return item; + + } + + /** + * Searches the DB for a FeedImage of the given id. + * + * @param id + * The id of the object + * @return The found object + * */ + private static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { + Cursor cursor = adapter.getImageOfFeedCursor(id); + if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { + throw new SQLException("No FeedImage found at index: " + id); + } + FeedImage image = new FeedImage(id, cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_TITLE)), + cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_FILE_URL)), + cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)), + cursor.getInt(cursor + .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0); + cursor.close(); + return image; + } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 420264840..5ec5df2c8 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -583,10 +583,49 @@ public class PodDBAdapter { return c; } + /** + * Returns a cursor which contains all feed items in the queue. The returned + * cursor uses the SEL_FI_SMALL selection. + */ public final Cursor getQueueCursor() { open(); - Cursor c = db.query(TABLE_NAME_QUEUE, null, null, null, null, null, - null); + Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, + "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE, + TABLE_NAME_FEED_ITEMS + "." + KEY_ID, + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM }, null, null, + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM); + return c; + } + + /** + * Returns a cursor which contains all feed items in the unread items list. + * The returned cursor uses the SEL_FI_SMALL selection. + */ + public final Cursor getUnreadItemsCursor() { + open(); + Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_READ + + "=0", null, null, null, KEY_PUBDATE + " DESC"); + return c; + } + + /** + * Returns a cursor which contains feed media objects with a playback + * completion date in descending order. + * + * @param limit + * The maximum row count of the returned cursor. Must be an + * integer >= 0. + * @throws IllegalArgumentException + * if limit < 0 + */ + public final Cursor getCompletedMediaCursor(int limit) { + if (limit < 0) { + throw new IllegalArgumentException("Limit must be >= 0"); + } + open(); + Cursor c = db.query(CREATE_TABLE_FEED_MEDIA, null, + KEY_PLAYBACK_COMPLETION_DATE + " IS NOT NULL", null, null, + null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit); return c; } @@ -635,25 +674,18 @@ public class PodDBAdapter { return buffer.toString(); } - /** - * Searches the DB for a FeedImage of the given id. - * - * @param id - * The id of the object - * @return The found object - * */ - public final FeedImage getFeedImage(final long id) throws SQLException { - Cursor cursor = this.getImageOfFeedCursor(id); - if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { - throw new SQLException("No FeedImage found at index: " + id); - } - FeedImage image = new FeedImage(id, cursor.getString(cursor - .getColumnIndex(KEY_TITLE)), cursor.getString(cursor - .getColumnIndex(KEY_FILE_URL)), cursor.getString(cursor - .getColumnIndex(KEY_DOWNLOAD_URL)), cursor.getInt(cursor - .getColumnIndex(KEY_DOWNLOADED)) > 0); - cursor.close(); - return image; + public final Cursor getFeedCursor(final long id) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null, + null, null, null); + return c; + } + + public final Cursor getFeedItemCursor(final long id) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null, + null, null, null); + return c; } /** -- cgit v1.2.3 From e89f1a9b1fa3719495c81cd90d49bbb723cf8f48 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 4 May 2013 00:22:00 +0200 Subject: Implemented DBWriter-methods --- src/de/danoeh/antennapod/storage/DBReader.java | 61 +++- src/de/danoeh/antennapod/storage/DBWriter.java | 402 ++++++++++++++++++++- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 49 ++- 3 files changed, 481 insertions(+), 31 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 3550aa6a2..97c52d749 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -246,25 +246,29 @@ public final class DBReader { } return null; } - - public static List getQueue(Context context) { + + static List getQueue(Context context, PodDBAdapter adapter) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting queue"); - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemlistCursor = adapter.getQueueCursor(); List items = extractItemlistFromCursor(adapter, itemlistCursor); itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); + Collections.sort(items, new FeedItemPubdateComparator()); - adapter.close(); + return items; + } - Collections.sort(items, new FeedItemPubdateComparator()); + public static List getQueue(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting queue"); + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List items = getQueue(context, adapter); + adapter.close(); return items; } @@ -287,8 +291,26 @@ public final class DBReader { return items; } - public static List getPlaybackHistory() { - return null; + public static List getPlaybackHistory(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading playback history"); + final int PLAYBACK_HISTORY_SIZE = 50; + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE); + String[] itemIds = new String[mediaCursor.getCount()]; + for (int i = 0; i < itemIds.length; i++) { + itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_FEEDITEM_INDEX)); + } + mediaCursor.close(); + Cursor itemCursor = adapter.getFeedItemCursor(itemIds); + List items = extractItemlistFromCursor(adapter, itemCursor); + itemCursor.close(); + + adapter.close(); + return items; } public static List getDownloadLog(Context context) { @@ -345,21 +367,30 @@ public final class DBReader { adapter.close(); return feed; } - - public FeedItem getFeedItem(final Context context, final long itemId) { + + static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) { if (AppConfig.DEBUG) Log.d(TAG, "Loading feeditem with id " + itemId); FeedItem item = null; - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemCursor = adapter.getFeedItemCursor(itemId); + Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId)); if (itemCursor.moveToFirst()) { List list = extractItemlistFromCursor(adapter, itemCursor); if (list.size() > 0) { item = list.get(0); } } + return item; + + } + + public static FeedItem getFeedItem(final Context context, final long itemId) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feeditem with id " + itemId); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + FeedItem item = getFeedItem(context, itemId, adapter); adapter.close(); return item; diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 76a1ad98d..996c3e028 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,63 +1,441 @@ package de.danoeh.antennapod.storage; +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.PlaybackPreferences; +import de.danoeh.antennapod.service.PlaybackService; public class DBWriter { private static final String TAG = "DBWriter"; - + + private static final ExecutorService dbExec; + static { + dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); + } + private DBWriter() { } - public static boolean deleteFeedMedia(final Context context, - final long mediaId) { - return false; + public static void deleteFeedMediaOfItem(final Context context, + final long itemId) { + dbExec.submit(new Runnable() { + @Override + public void run() { + final FeedItem item = DBReader.getFeedItem(context, itemId); + if (item != null && item.hasMedia()) { + final FeedMedia media = item.getMedia(); + boolean result = false; + if (media.isDownloaded()) { + // delete downloaded media file + File mediaFile = new File(media.getFile_url()); + if (mediaFile.exists()) { + result = mediaFile.delete(); + } + media.setDownloaded(false); + media.setFile_url(null); + setFeedMedia(context, media); + + // If media is currently being played, change playback + // type to 'stream' and shutdown playback service + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) { + if (media.getId() == PlaybackPreferences + .getCurrentlyPlayingFeedMediaId()) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, + true); + editor.commit(); + } + if (PlaybackPreferences + .getCurrentlyPlayingFeedMediaId() == media + .getId()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Deleting File. Result: " + result); + } + } + }); } public static void deleteFeed(final Context context, final long feedId) { + dbExec.submit(new Runnable() { + @Override + public void run() { + DownloadRequester requester = DownloadRequester.getInstance(); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context + .getApplicationContext()); + final Feed feed = DBReader.getFeed(context, feedId); + if (feed != null) { + if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA + && PlaybackPreferences.getLastPlayedFeedId() == feed + .getId()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + -1); + editor.commit(); + } + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + // delete image file + if (feed.getImage() != null) { + if (feed.getImage().isDownloaded() + && feed.getImage().getFile_url() != null) { + File imageFile = new File(feed.getImage() + .getFile_url()); + imageFile.delete(); + } else if (requester.isDownloadingFile(feed.getImage())) { + requester.cancelDownload(context, feed.getImage()); + } + } + // delete stored media files and mark them as read + List queue = DBReader.getQueue(context); + boolean queueWasModified = false; + if (feed.getItems() == null) { + DBReader.getFeedItemList(context, feed); + } + for (FeedItem item : feed.getItems()) { + queueWasModified |= queue.remove(item); + if (item.getMedia() != null + && item.getMedia().isDownloaded()) { + File mediaFile = new File(item.getMedia() + .getFile_url()); + mediaFile.delete(); + } else if (item.getMedia() != null + && requester.isDownloadingFile(item.getMedia())) { + requester.cancelDownload(context, item.getMedia()); + } + } + if (queueWasModified) { + adapter.setQueue(queue); + } + adapter.removeFeed(feed); + adapter.close(); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + } + }); } public static void clearPlaybackHistory(final Context context) { + dbExec.submit(new Runnable() { + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.clearPlaybackHistory(); + adapter.close(); + EventDistributor.getInstance() + .sendPlaybackHistoryUpdateBroadcast(); + } + }); } public static void addItemToPlaybackHistory(final Context context, - long itemId) { + final FeedItem item) { + if (item.hasMedia() + && item.getMedia().getPlaybackCompletionDate() != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Adding new item to playback history"); + EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); + } } - private static void removeItemFromPlaybackHistory(final Context context, - long itemId) { - + private static void cleanupDownloadLog(final PodDBAdapter adapter) { + final int DOWNLOAD_LOG_SIZE = 50; + final long logSize = adapter.getDownloadLogSize(); + if (logSize > DOWNLOAD_LOG_SIZE) { + if (AppConfig.DEBUG) + Log.d(TAG, "Cleaning up download log"); + adapter.removeDownloadLogItems(logSize - DOWNLOAD_LOG_SIZE); + } } public static void addDownloadStatus(final Context context, - final long statusId) { + final DownloadStatus status) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + adapter.setDownloadStatus(status); + cleanupDownloadLog(adapter); + adapter.close(); + EventDistributor.getInstance().sendDownloadLogUpdateBroadcast(); + } + }); } public static void addQueueItemAt(final Context context, final long itemId, final int index, final boolean performAutoDownload) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); + FeedItem item = null; + + if (queue != null) { + boolean queueModified = false; + boolean unreadItemsModfied = false; + + if (!itemListContains(queue, itemId)) { + item = DBReader.getFeedItem(context, itemId); + if (item != null) { + queue.add(index, item); + queueModified = true; + if (!item.isRead()) { + item.setRead(true); + unreadItemsModfied = true; + } + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + if (unreadItemsModfied && item != null) { + adapter.setSingleFeedItem(item); + EventDistributor.getInstance() + .sendUnreadItemsUpdateBroadcast(); + } + } + adapter.close(); + if (performAutoDownload) { + + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); + + } + }.start(); + } + + } + }); } public static void addQueueItem(final Context context, final long... itemIds) { + if (itemIds.length > 0) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader.getQueue(context, + adapter); + + if (queue != null) { + boolean queueModified = false; + boolean unreadItemsModfied = false; + List itemsToSave = new LinkedList(); + for (int i = 0; i < itemIds.length; i++) { + if (!itemListContains(queue, itemIds[i])) { + final FeedItem item = DBReader.getFeedItem( + context, itemIds[i]); + + if (item != null) { + queue.add(item); + queueModified = true; + if (!item.isRead()) { + item.setRead(true); + itemsToSave.add(item); + unreadItemsModfied = true; + } + } + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + if (unreadItemsModfied) { + adapter.setFeedItemlist(itemsToSave); + EventDistributor.getInstance() + .sendUnreadItemsUpdateBroadcast(); + } + } + adapter.close(); + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); + + } + }.start(); + } + }); + } } public static void clearQueue(final Context context) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.clearQueue(); + adapter.close(); + + EventDistributor.getInstance().sendQueueUpdateBroadcast(); + } + }); } public static void removeQueueItem(final Context context, final long itemId, final boolean performAutoDownload) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); + FeedItem item = null; + + if (queue != null) { + boolean queueModified = false; + + if (itemListContains(queue, itemId)) { + item = DBReader.getFeedItem(context, itemId); + if (item != null) { + queue.remove(item); + queueModified = true; + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + } + adapter.close(); + if (performAutoDownload) { + + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); + + } + }.start(); + } + } + }); } - public void moveQueueItem(final Context context, int from, int to, - boolean broadcastUpdate) { + public void moveQueueItem(final Context context, final int from, + final int to, final boolean broadcastUpdate) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); + + if (queue != null) { + if (from >= 0 && from < queue.size() && to >= 0 + && to < queue.size()) { + final FeedItem item = queue.remove(from); + queue.add(to, item); + adapter.setQueue(queue); + if (broadcastUpdate) { + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + + } + } + adapter.close(); + } + }); } - void addNewFeed(final Context context, final long feedId) { + void addNewFeed(final Context context, final Feed feed) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + }); + } + private static void setFeedMedia(final Context context, + final FeedMedia media) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setMedia(media); + adapter.close(); + + } + + private static boolean itemListContains(List items, long itemId) { + for (FeedItem item : items) { + if (item.getId() == itemId) { + return true; + } + } + return false; } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 5ec5df2c8..ef9b4bfe0 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -340,6 +340,15 @@ public class PodDBAdapter { db.setTransactionSuccessful(); db.endTransaction(); } + + public void setFeedItemlist(List items) { + db.beginTransaction(); + for (FeedItem item : items) { + setFeedItem(item); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } public long setSingleFeedItem(FeedItem item) { db.beginTransaction(); @@ -439,6 +448,22 @@ public class PodDBAdapter { return status.getId(); } + public long getDownloadLogSize() { + Cursor result = db.rawQuery("SELECT COUNT(?) AS ? FROM ?", + new String[] { KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG }); + long count = result.getLong(KEY_ID_INDEX); + result.close(); + return count; + } + + public void removeDownloadLogItems(long count) { + if (count > 0) { + db.rawQuery("DELETE FROM ? ORDER BY ? ASC LIMIT ?", + new String[] { TABLE_NAME_DOWNLOAD_LOG, + KEY_COMPLETION_DATE, Long.toString(count) }); + } + } + public void setQueue(List queue) { ContentValues values = new ContentValues(); db.beginTransaction(); @@ -454,6 +479,10 @@ public class PodDBAdapter { db.setTransactionSuccessful(); db.endTransaction(); } + + public void clearQueue() { + db.delete(TABLE_NAME_QUEUE, null, null); + } public void removeFeedMedia(FeedMedia media) { db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?", @@ -502,6 +531,12 @@ public class PodDBAdapter { new String[] { String.valueOf(remove.getId()) }); } + public void clearPlaybackHistory() { + ContentValues values = new ContentValues(); + values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); + db.update(TABLE_NAME_FEED_MEDIA, values, null, null); + } + /** * Get all Feeds from the Feed Table. * @@ -681,11 +716,17 @@ public class PodDBAdapter { return c; } - public final Cursor getFeedItemCursor(final long id) { + public final Cursor getFeedItemCursor(final String... ids) { + if (ids.length > IN_OPERATOR_MAXIMUM) { + throw new IllegalArgumentException( + "number of IDs must not be larger than " + + IN_OPERATOR_MAXIMUM); + } + open(); - Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null, - null, null, null); - return c; + return db.query(TABLE_NAME_FEED_ITEMS, null, KEY_ID + " IN " + + buildInOperator(ids.length), ids, null, null, null); + } /** -- cgit v1.2.3 From 11fb8589090d0715bb907b5f1e2c77644e3c86ff Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Fri, 17 May 2013 23:20:23 +0200 Subject: Implemented several DBTasks and DBWriter methods --- src/de/danoeh/antennapod/storage/DBReader.java | 15 ++ src/de/danoeh/antennapod/storage/DBTasks.java | 183 +++++++++++++++++---- src/de/danoeh/antennapod/storage/DBWriter.java | 101 +++++++++++- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 53 +++++- 4 files changed, 314 insertions(+), 38 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 97c52d749..739ecd4be 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -290,6 +290,21 @@ public final class DBReader { return items; } + + public static long[] getUnreadItemIds(Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor cursor = adapter.getUnreadItemIdsCursor(); + long[] itemIds = new long[cursor.getCount()]; + int i = 0; + if (cursor.moveToFirst()) { + do { + itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + i++; + } while (cursor.moveToNext()); + } + return itemIds; + } public static List getPlaybackHistory(final Context context) { if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 75477d463..d71a5498b 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -1,29 +1,58 @@ package de.danoeh.antennapod.storage; +import java.util.Iterator; +import java.util.List; + import android.content.Context; +import android.content.Intent; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; public final class DBTasks { private static final String TAG = "DBTasks"; - + private DBTasks() { } - public static void playMedia(final Context context, final long mediaId, + public static void playMedia(final Context context, final FeedMedia media, boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { - - } - - public static void markItemRead(final Context context, final long itemId, - final boolean read, boolean resetMediaPosition) { - } - - public static void markFeedRead(final Context context, final long feedId) { - - } - - public static void markAllItemsRead(final Context context) { + try { + if (!shouldStream) { + if (media.fileExists() == false) { + throw new MediaFileNotFoundException( + "No episode was found at " + media.getFile_url(), + media); + } + } + // Start playback Service + Intent launchIntent = new Intent(context, PlaybackService.class); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); + launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, + startWhenPrepared); + launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, + shouldStream); + launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, + true); + context.startService(launchIntent); + if (showPlayer) { + // Launch Mediaplayer + context.startActivity(PlaybackService.getPlayerActivityIntent( + context, media)); + } + DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false); + } catch (MediaFileNotFoundException e) { + e.printStackTrace(); + if (media.isPlaying()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + notifyMissingFeedMediaFile(context, media); + } } public static void refreshAllFeeds(final Context context) { @@ -37,41 +66,137 @@ public final class DBTasks { } public static void notifyMissingFeedMediaFile(final Context context, - final long mediaId) { + final FeedMedia media) { + } + + public static void downloadAllItemsInQueue(final Context context) { } - - public static void downloadAllItemsInQueue(final Context context) {} public static void refreshFeed(final Context context, final long feedId) { } - - public static void downloadFeedItem(final Context context, long... itemIds) {} - + + public static void downloadFeedItem(final Context context, long... itemIds) { + } + static void downloadFeedItem(boolean performAutoCleanup, final Context context, final long... itemIds) - throws DownloadRequestException {} + throws DownloadRequestException { + } public static void autodownloadUndownloadedItems(Context context) { } - + private static int getPerformAutoCleanupArgs(final int episodeNumber) { return 0; } - - public static void performAutoCleanup(final Context context) {} - - private static int performAutoCleanup(final Context context, final int episodeNumber) { + + public static void performAutoCleanup(final Context context) { + } + + private static int performAutoCleanup(final Context context, + final int episodeNumber) { return 0; } - public static void enqueueAllNewItems(final Context context) {} + public static void enqueueAllNewItems(final Context context) { + long[] unreadItems = DBReader.getUnreadItemIds(context); + DBWriter.addQueueItem(context, unreadItems); + } + + public static FeedItem getQueueSuccessorOfItem(Context context, + final long itemId) { + FeedItem result = null; + List queue = DBReader.getQueue(context); + if (queue != null) { + Iterator iterator = queue.iterator(); + while (iterator.hasNext()) { + FeedItem item = iterator.next(); + if (item.getId() == itemId) { + if (iterator.hasNext()) { + result = iterator.next(); + } + break; + } + } + } + return result; + } - public static FeedItem getQueueSuccessorOfItem(final long itemId) { + private static Feed searchFeedByIdentifyingValue(Context context, + String identifier) { + List feeds = DBReader.getFeedList(context); + for (Feed feed : feeds) { + if (feed.getIdentifyingValue().equals(identifier)) { + return feed; + } + } return null; } - public static Feed updateFeed(final Context context, final long feedId) { + /** Get a FeedItem by its identifying value. */ + private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, + String identifier) { + for (FeedItem item : feed.getItems()) { + if (item.getIdentifyingValue().equals(identifier)) { + return item; + } + } return null; } + + public static synchronized Feed updateFeed(final Context context, final Feed newFeed) { + // Look up feed in the feedslist + final Feed savedFeed = searchFeedByIdentifyingValue(context, + newFeed.getIdentifyingValue()); + if (savedFeed == null) { + if (AppConfig.DEBUG) + Log.d(TAG, + "Found no existing Feed with title " + + newFeed.getTitle() + ". Adding as new one."); + // Add a new Feed + DBWriter.addNewFeed(context, newFeed); + return newFeed; + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Feed with title " + newFeed.getTitle() + + " already exists. Syncing new with existing one."); + + savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); + if (savedFeed.compareWithOther(newFeed)) { + if (AppConfig.DEBUG) + Log.d(TAG, + "Feed has updated attribute values. Updating old feed's attributes"); + savedFeed.updateFromOther(newFeed); + } + // Look for new or updated Items + for (int idx = 0; idx < newFeed.getItems().size(); idx++) { + final FeedItem item = newFeed.getItems().get(idx); + FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, + item.getIdentifyingValue()); + if (oldItem == null) { + // item is new + final int i = idx; + item.setFeed(savedFeed); + savedFeed.getItems().add(i, item); + DBWriter.markItemRead(context, item.getId(), false); + } else { + oldItem.updateFromOther(item); + } + } + // update attributes + savedFeed.setLastUpdate(newFeed.getLastUpdate()); + savedFeed.setType(newFeed.getType()); + DBWriter.setCompleteFeed(context, savedFeed); + new Thread() { + @Override + public void run() { + autodownloadUndownloadedItems(context); + } + }.start(); + return savedFeed; + } + + } + } diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 996c3e028..aece811ca 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -10,6 +10,7 @@ import java.util.concurrent.ThreadFactory; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; @@ -406,7 +407,76 @@ public class DBWriter { }); } - void addNewFeed(final Context context, final Feed feed) { + public static void markItemRead(final Context context, final long itemId, + final boolean read) { + markItemRead(context, itemId, read, 0, false); + } + + public static void markItemRead(final Context context, final long itemId, + final boolean read, final long mediaId, + final boolean resetMediaPosition) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedItemRead(read, itemId, mediaId, + resetMediaPosition); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + } + + public static void markFeedRead(final Context context, final long feedId) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId); + long[] itemIds = new long[itemCursor.getCount()]; + itemCursor.moveToFirst(); + for (int i = 0; i < itemIds.length; i++) { + itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + } + itemCursor.close(); + adapter.setFeedItemRead(true, itemIds); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + + } + + public static void markAllItemsRead(final Context context) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor itemCursor = adapter.getUnreadItemsCursor(); + long[] itemIds = new long[itemCursor.getCount()]; + itemCursor.moveToFirst(); + for (int i = 0; i < itemIds.length; i++) { + itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + } + itemCursor.close(); + adapter.setFeedItemRead(true, itemIds); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + + } + + static void addNewFeed(final Context context, final Feed feed) { dbExec.submit(new Runnable() { @Override @@ -420,13 +490,34 @@ public class DBWriter { } }); } + + static void setCompleteFeed(final Context context, final Feed feed) { + dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + }}); + + } private static void setFeedMedia(final Context context, final FeedMedia media) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setMedia(media); - adapter.close(); + dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setMedia(media); + adapter.close(); + }}); + } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index ef9b4bfe0..72c96b961 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -340,7 +340,7 @@ public class PodDBAdapter { db.setTransactionSuccessful(); db.endTransaction(); } - + public void setFeedItemlist(List items) { db.beginTransaction(); for (FeedItem item : items) { @@ -400,6 +400,39 @@ public class PodDBAdapter { return item.getId(); } + public void setFeedItemRead(boolean read, long itemId, long mediaId, + boolean resetMediaPosition) { + db.beginTransaction(); + ContentValues values = new ContentValues(); + + values.put(KEY_READ, read); + db.update(TABLE_NAME_FEED_ITEMS, values, "?=?", new String[] { KEY_ID, + Long.toString(itemId) }); + + if (resetMediaPosition) { + values.clear(); + values.put(KEY_POSITION, 0); + db.update(TABLE_NAME_FEED_MEDIA, values, "?=?", new String[] { + KEY_ID, Long.toString(mediaId) }); + } + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public void setFeedItemRead(boolean read, long... itemIds) { + db.beginTransaction(); + ContentValues values = new ContentValues(); + for (long id : itemIds) { + values.clear(); + values.put(KEY_READ, read); + db.update(TABLE_NAME_FEED_ITEMS, values, "?=?", new String[] { + KEY_ID, Long.toString(id) }); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + public void setChapters(FeedItem item) { ContentValues values = new ContentValues(); for (Chapter chapter : item.getChapters()) { @@ -479,7 +512,7 @@ public class PodDBAdapter { db.setTransactionSuccessful(); db.endTransaction(); } - + public void clearQueue() { db.delete(TABLE_NAME_QUEUE, null, null); } @@ -557,10 +590,14 @@ public class PodDBAdapter { * @return The cursor of the query * */ public final Cursor getAllItemsOfFeedCursor(final Feed feed) { + return getAllItemsOfFeedCursor(feed.getId()); + } + + public final Cursor getAllItemsOfFeedCursor(final long feedId) { open(); Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED - + "=?", new String[] { String.valueOf(feed.getId()) }, null, - null, null); + + "=?", new String[] { String.valueOf(feedId) }, null, null, + null); return c; } @@ -642,6 +679,14 @@ public class PodDBAdapter { + "=0", null, null, null, KEY_PUBDATE + " DESC"); return c; } + + public final Cursor getUnreadItemIdsCursor() { + open(); + 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 feed media objects with a playback -- cgit v1.2.3 From a704a33e2b31a7312f35f33251faaf1017b982d9 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 19 May 2013 17:32:15 +0200 Subject: Split DownloadStatus into two separate classes DownloadStatus is now used only for finished downloads, whereas DownloadRequest is used for running/unfinished downloads --- src/de/danoeh/antennapod/storage/DBReader.java | 2 +- src/de/danoeh/antennapod/storage/DBWriter.java | 2 +- .../antennapod/storage/DownloadRequester.java | 36 +++++++++++----------- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 20 ++++-------- 4 files changed, 26 insertions(+), 34 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 739ecd4be..e731a360e 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -10,7 +10,6 @@ import android.database.Cursor; import android.database.SQLException; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.asynctask.DownloadStatus; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedImage; @@ -19,6 +18,7 @@ import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.VorbisCommentChapter; +import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index aece811ca..1a2468782 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -14,13 +14,13 @@ import android.database.Cursor; import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.asynctask.DownloadStatus; import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.service.download.DownloadStatus; public class DBWriter { private static final String TAG = "DBWriter"; diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 29bd764dd..30abc4491 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import android.content.Context; import android.content.Intent; @@ -17,6 +18,7 @@ import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.service.download.DownloadRequest; import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.util.FileNameGenerator; import de.danoeh.antennapod.util.URLChecker; @@ -30,10 +32,10 @@ public class DownloadRequester { private static DownloadRequester downloader; - Map downloads; + Map downloads; private DownloadRequester() { - downloads = new ConcurrentHashMap(); + downloads = new ConcurrentHashMap(); } public static DownloadRequester getInstance() { @@ -83,11 +85,13 @@ public class DownloadRequester { Log.d(TAG, "Requesting download of url " + item.getDownload_url()); item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); - item.setFile_url(dest.toString()); - downloads.put(item.getDownload_url(), item); - DownloadService.Request request = new DownloadService.Request( - item.getFile_url(), item.getDownload_url()); + DownloadRequest request = new DownloadRequest(item.getFile_url(), + item.getDownload_url(), item.getHumanReadableIdentifier(), + item.getId(), item.getTypeAsInt()); + + downloads.put(request.getSource(), request); + if (!DownloadService.isRunning) { Intent launchIntent = new Intent(context, DownloadService.class); @@ -112,8 +116,8 @@ public class DownloadRequester { */ private boolean isFilenameAvailable(String path) { for (String key : downloads.keySet()) { - FeedFile f = downloads.get(key); - if (f.getFile_url() != null && f.getFile_url().equals(path)) { + DownloadRequest r = downloads.get(key); + if (StringUtils.equals(r.getDestination(), path)) { if (AppConfig.DEBUG) Log.d(TAG, path + " is already used by another requested download"); @@ -194,8 +198,8 @@ public class DownloadRequester { /** Returns true if there is at least one Feed in the downloads queue. */ public boolean isDownloadingFeeds() { - for (FeedFile f : downloads.values()) { - if (f.getClass() == Feed.class) { + for (DownloadRequest r : downloads.values()) { + if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { return true; } } @@ -210,7 +214,7 @@ public class DownloadRequester { return false; } - public FeedFile getDownload(String downloadUrl) { + public DownloadRequest getDownload(String downloadUrl) { return downloads.get(downloadUrl); } @@ -223,15 +227,11 @@ public class DownloadRequester { return downloads.isEmpty(); } - public FeedFile getDownloadAt(int index) { - return downloads.get(index); - } - /** Remove an object from the downloads-list of the requester. */ - public void removeDownload(FeedFile f) { - if (downloads.remove(f.getDownload_url()) == null) { + public void removeDownload(DownloadRequest r) { + if (downloads.remove(r.getSource()) == null) { Log.e(TAG, - "Could not remove object with url " + f.getDownload_url()); + "Could not remove object with url " + r.getSource()); } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 72c96b961..dcc9533a5 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -14,12 +14,12 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.asynctask.DownloadStatus; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.service.download.DownloadStatus; /** * Implements methods for accessing the database @@ -456,16 +456,8 @@ public class PodDBAdapter { * */ public long setDownloadStatus(DownloadStatus status) { ContentValues values = new ContentValues(); - if (status.getFeedFile() != null) { - values.put(KEY_FEEDFILE, status.getFeedFile().getId()); - if (status.getFeedFile().getClass() == Feed.class) { - values.put(KEY_FEEDFILETYPE, Feed.FEEDFILETYPE_FEED); - } else if (status.getFeedFile().getClass() == FeedImage.class) { - values.put(KEY_FEEDFILETYPE, FeedImage.FEEDFILETYPE_FEEDIMAGE); - } else if (status.getFeedFile().getClass() == FeedMedia.class) { - values.put(KEY_FEEDFILETYPE, FeedMedia.FEEDFILETYPE_FEEDMEDIA); - } - } + values.put(KEY_FEEDFILE, status.getFeedfileId()); + values.put(KEY_FEEDFILETYPE, status.getFeedfileType()); values.put(KEY_REASON, status.getReason()); values.put(KEY_SUCCESSFUL, status.isSuccessful()); values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime()); @@ -679,11 +671,11 @@ public class PodDBAdapter { + "=0", null, null, null, KEY_PUBDATE + " DESC"); return c; } - + public final Cursor getUnreadItemIdsCursor() { open(); - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID}, KEY_READ - + "=0", null, null, null, KEY_PUBDATE + " DESC"); + Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[] { KEY_ID }, + KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC"); return c; } -- cgit v1.2.3 From c6545a5643886b653422fcd1f722fdb1d34f6f9e Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 26 May 2013 03:47:57 +0200 Subject: Implemented refresh, auto-download, auto-cleanup methods --- src/de/danoeh/antennapod/storage/DBReader.java | 47 ++++ src/de/danoeh/antennapod/storage/DBTasks.java | 301 ++++++++++++++++++++- src/de/danoeh/antennapod/storage/DBWriter.java | 2 +- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 30 ++ 4 files changed, 365 insertions(+), 15 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index e731a360e..5ae47f32b 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -47,6 +47,26 @@ public final class DBReader { feedlistCursor.close(); return feeds; } + + static List getExpiredFeedsList(final Context context, final long expirationTime) { + if (AppConfig.DEBUG) + Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); + List feeds = new ArrayList(feedlistCursor.getCount()); + + if (feedlistCursor.moveToFirst()) { + do { + Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); + feeds.add(feed); + } while (feedlistCursor.moveToNext()); + } + feedlistCursor.close(); + return feeds; + } public static void loadFeedDataOfFeedItemlist(Context context, List items) { @@ -271,6 +291,25 @@ public final class DBReader { adapter.close(); return items; } + + public static List getDownloadedItems(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting downloaded items"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getDownloadedItemsCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + loadFeedDataOfFeedItemlist(context, items); + Collections.sort(items, new FeedItemPubdateComparator()); + + adapter.close(); + return items; + + } public static List getUnreadItemsList(Context context) { if (AppConfig.DEBUG) @@ -410,6 +449,14 @@ public final class DBReader { return item; } + + public static int getNumberOfDownloadedEpisodes(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final int result = adapter.getNumberOfDownloadedEpisodes(); + adapter.close(); + return result; + } /** * Searches the DB for a FeedImage of the given id. diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index d71a5498b..9b874768c 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -1,16 +1,28 @@ package de.danoeh.antennapod.storage; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import android.content.Context; import android.content.Intent; import android.util.Log; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.util.DownloadError; +import de.danoeh.antennapod.util.NetworkUtils; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; public final class DBTasks { @@ -55,47 +67,308 @@ public final class DBTasks { } } - public static void refreshAllFeeds(final Context context) { + private static ReentrantLock refreshAllFeedsLock = new ReentrantLock(); + + public static void refreshAllFeeds(final Context context, + final List feeds) { + if (refreshAllFeedsLock.tryLock()) { + new Thread() { + public void run() { + refreshFeeds(context, feeds); + refreshAllFeedsLock.unlock(); + } + }.start(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, + "Ignoring request to refresh all feeds: Refresh lock is locked"); + } } public void refreshExpiredFeeds(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Refreshing expired feeds"); + + new Thread() { + public void run() { + long millis = UserPreferences.getUpdateInterval(); + + if (millis > 0) { + long now = Calendar.getInstance().getTime().getTime(); + + // Allow a 10 minute window + millis -= 10 * 60 * 1000; + List feedList = DBReader.getExpiredFeedsList(context, + now - millis); + if (feedList.size() > 0) { + refreshFeeds(context, feedList); + } + } + } + }.start(); + } + + private static void refreshFeeds(final Context context, + final List feedList) { + + for (Feed feed : feedList) { + try { + refreshFeed(context, feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus( + context, + new DownloadStatus(feed, feed + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, false, e + .getMessage())); + } + } + + } + + /** Updates a specific feed. */ + private static void refreshFeed(Context context, Feed feed) + throws DownloadRequestException { + DownloadRequester.getInstance().downloadFeed(context, + new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); } public static void notifyInvalidImageFile(final Context context, - final long imageId) { + final FeedImage image) { + Log.i(TAG, + "The feedmanager was notified about an invalid image download. It will now try to redownload the image file"); + try { + DownloadRequester.getInstance().downloadImage(context, image); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Log.w(TAG, "Failed to download invalid feed image"); + } } public static void notifyMissingFeedMediaFile(final Context context, final FeedMedia media) { + Log.i(TAG, + "The feedmanager was notified about a missing episode. It will update its database now."); + media.setDownloaded(false); + media.setFile_url(null); + DBWriter.setFeedMedia(context, media); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); } public static void downloadAllItemsInQueue(final Context context) { + new Thread() { + public void run() { + List queue = DBReader.getQueue(context); + if (!queue.isEmpty()) { + try { + downloadFeedItems(context, + queue.toArray(new FeedItem[queue.size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + } + } + }.start(); } - public static void refreshFeed(final Context context, final long feedId) { + public static void downloadFeedItems(final Context context, + FeedItem... items) throws DownloadRequestException { + downloadFeedItems(true, context, items); } - public static void downloadFeedItem(final Context context, long... itemIds) { + private static void downloadFeedItems(boolean performAutoCleanup, + final Context context, final FeedItem... items) + throws DownloadRequestException { + final DownloadRequester requester = DownloadRequester.getInstance(); + + if (performAutoCleanup) { + new Thread() { + + @Override + public void run() { + performAutoCleanup(context, + getPerformAutoCleanupArgs(context, items.length)); + } + + }.start(); + } + for (FeedItem item : items) { + if (item.getMedia() != null + && !requester.isDownloadingFile(item.getMedia()) + && !item.getMedia().isDownloaded()) { + if (items.length > 1) { + try { + requester.downloadMedia(context, item.getMedia()); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus(context, + new DownloadStatus(item.getMedia(), item + .getMedia() + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, + false, e.getMessage())); + } + } else { + requester.downloadMedia(context, item.getMedia()); + } + } + } } - static void downloadFeedItem(boolean performAutoCleanup, - final Context context, final long... itemIds) - throws DownloadRequestException { + private static int getNumberOfUndownloadedEpisodes( + final List queue, final List unreadItems) { + int counter = 0; + for (FeedItem item : queue) { + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + counter++; + } + } + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + counter++; + } + } + return counter; } - public static void autodownloadUndownloadedItems(Context context) { + public static void autodownloadUndownloadedItems(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Performing auto-dl of undownloaded episodes"); + if (NetworkUtils.autodownloadNetworkAvailable(context) + && UserPreferences.isEnableAutodownload()) { + final List queue = DBReader.getQueue(context); + final List unreadItems = DBReader + .getUnreadItemsList(context); + + int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue, + unreadItems); + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(context); + int deletedEpisodes = performAutoCleanup(context, + getPerformAutoCleanupArgs(context, undownloadedEpisodes)); + int episodeSpaceLeft = undownloadedEpisodes; + boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences + .getEpisodeCacheSizeUnlimited(); + + if (!cacheIsUnlimited + && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes + + undownloadedEpisodes) { + episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() + - (downloadedEpisodes - deletedEpisodes); + } + + List itemsToDownload = new ArrayList(); + if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { + for (int i = 0; i < queue.size(); i++) { // ignore playing item + FeedItem item = queue.get(i); + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Enqueueing " + itemsToDownload.size() + + " items for download"); + + try { + downloadFeedItems(false, context, + itemsToDownload.toArray(new FeedItem[itemsToDownload + .size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + + } } - private static int getPerformAutoCleanupArgs(final int episodeNumber) { + private static int getPerformAutoCleanupArgs(Context context, + final int episodeNumber) { + if (episodeNumber >= 0 + && UserPreferences.getEpisodeCacheSize() != UserPreferences + .getEpisodeCacheSizeUnlimited()) { + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(context); + if (downloadedEpisodes + episodeNumber >= UserPreferences + .getEpisodeCacheSize()) { + + return downloadedEpisodes + episodeNumber + - UserPreferences.getEpisodeCacheSize(); + } + } return 0; } public static void performAutoCleanup(final Context context) { + performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); } private static int performAutoCleanup(final Context context, final int episodeNumber) { - return 0; + List candidates = DBReader.getDownloadedItems(context); + List queue = DBReader.getQueue(context); + List delete; + for (FeedItem item : candidates) { + if (item.hasMedia() && item.getMedia().isDownloaded() + && !queue.contains(item) && item.isRead()) { + candidates.add(item); + } + + } + + Collections.sort(candidates, new Comparator() { + @Override + public int compare(FeedItem lhs, FeedItem rhs) { + Date l = lhs.getMedia().getPlaybackCompletionDate(); + Date r = rhs.getMedia().getPlaybackCompletionDate(); + + if (l == null) { + l = new Date(0); + } + if (r == null) { + r = new Date(0); + } + return l.compareTo(r); + } + }); + + if (candidates.size() > episodeNumber) { + delete = candidates.subList(0, episodeNumber); + } else { + delete = candidates; + } + + for (FeedItem item : delete) { + DBWriter.deleteFeedMediaOfItem(context, item.getId()); + } + + int counter = delete.size(); + + if (AppConfig.DEBUG) + Log.d(TAG, String.format( + "Auto-delete deleted %d episodes (%d requested)", counter, + episodeNumber)); + + return counter; } public static void enqueueAllNewItems(final Context context) { @@ -132,7 +405,7 @@ public final class DBTasks { } return null; } - + /** Get a FeedItem by its identifying value. */ private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, String identifier) { @@ -144,8 +417,8 @@ public final class DBTasks { return null; } - - public static synchronized Feed updateFeed(final Context context, final Feed newFeed) { + public static synchronized Feed updateFeed(final Context context, + final Feed newFeed) { // Look up feed in the feedslist final Feed savedFeed = searchFeedByIdentifyingValue(context, newFeed.getIdentifyingValue()); @@ -161,7 +434,7 @@ public final class DBTasks { if (AppConfig.DEBUG) Log.d(TAG, "Feed with title " + newFeed.getTitle() + " already exists. Syncing new with existing one."); - + savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); if (savedFeed.compareWithOther(newFeed)) { if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 1a2468782..b5f9acdad 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -506,7 +506,7 @@ public class DBWriter { } - private static void setFeedMedia(final Context context, + static void setFeedMedia(final Context context, final FeedMedia media) { dbExec.submit(new Runnable() { diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index dcc9533a5..6ff22cb55 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -10,6 +10,7 @@ import android.database.DatabaseUtils; import android.database.MergeCursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; @@ -574,6 +575,14 @@ public class PodDBAdapter { return c; } + public final Cursor getExpiredFeedsCursor(long expirationTime) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, "?0", + SEL_FI_SMALL); + return c; + } + /** * Returns a cursor which contains feed media objects with a playback * completion date in descending order. @@ -766,6 +786,16 @@ public class PodDBAdapter { } + public final int getNumberOfDownloadedEpisodes() { + + Cursor c = db.rawQuery( + "SELECT COUNT(DISTINCT ?) AS count FROM ? WHERE ?>0", + new String[] { KEY_ID, TABLE_NAME_FEED_MEDIA, KEY_DOWNLOADED }); + final int result = c.getInt(0); + c.close(); + return result; + } + /** * Uses DatabaseUtils to escape a search query and removes ' at the * beginning and the end of the string returned by the escape method. -- cgit v1.2.3 From b83656049d0453012d29eb67f74a6352ce246689 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 26 May 2013 03:53:10 +0200 Subject: Added search methods to DBTasks --- src/de/danoeh/antennapod/storage/DBTasks.java | 154 ++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 9b874768c..eceb3c1b4 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -11,6 +11,8 @@ import java.util.concurrent.locks.ReentrantLock; import android.content.Context; import android.content.Intent; +import android.database.Cursor; +import android.os.Handler; import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.EventDistributor; @@ -469,7 +471,159 @@ public final class DBTasks { }.start(); return savedFeed; } + } + + /** + * Searches the descriptions of FeedItems of a specific feed for a given + * string. + * + * @param feed + * The feed whose items should be searched. + * @param query + * The search string + * @param callback + * A callback which will be used to return the search result + * */ + public void searchFeedItemDescription(final Context context, + final Feed feed, final String query, QueryTaskCallback callback) { + new Thread((new QueryTask(context, new Handler(), callback) { + + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemDescriptions(feed, + query); + setResult(searchResult); + } + })).start(); + } + + /** + * Searches the 'contentEncoded' field of FeedItems of a specific feed for a + * given string. + * + * @param feed + * The feed whose items should be searched. + * @param query + * The search string + * @param callback + * A callback which will be used to return the search result + * */ + public void searchFeedItemContentEncoded(final Context context, + final Feed feed, final String query, QueryTaskCallback callback) { + new Thread((new QueryTask(context, new Handler(), callback) { + + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemContentEncoded(feed, + query); + setResult(searchResult); + } + })).start(); + } + + /** Is called by a FeedManagerTask after completion. */ + public interface TaskCallback { + void onCompletion(V result); + } + + /** Is called by a FeedManager.QueryTask after completion. */ + public interface QueryTaskCallback { + void handleResult(Cursor result); + + void onCompletion(); + } + + /** A runnable that can post a callback to a handler after completion. */ + abstract class Task implements Runnable { + private Handler handler; + private TaskCallback callback; + private V result; + + /** + * Standard contructor. No callbacks are going to be posted to a + * handler. + */ + public Task() { + super(); + } + + /** + * The Task will post a Runnable to 'handler' that will execute the + * 'callback' after completion. + */ + public Task(Handler handler, TaskCallback callback) { + super(); + this.handler = handler; + this.callback = callback; + } + @Override + public final void run() { + execute(); + if (handler != null && callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onCompletion(result); + } + }); + } + } + + /** This method will be executed in the same thread as the run() method. */ + public abstract void execute(); + + public void setResult(V result) { + this.result = result; + } + } + + /** + * A runnable which should be used for database queries. The onCompletion + * method is executed on the database executor to handle Cursors correctly. + * This class automatically creates a PodDBAdapter object and closes it when + * it is no longer in use. + */ + abstract class QueryTask implements Runnable { + private QueryTaskCallback callback; + private Cursor result; + private Context context; + private Handler handler; + + public QueryTask(Context context, Handler handler, + QueryTaskCallback callback) { + this.callback = callback; + this.context = context; + this.handler = handler; + } + + @Override + public final void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + execute(adapter); + callback.handleResult(result); + if (result != null && !result.isClosed()) { + result.close(); + } + adapter.close(); + if (handler != null && callback != null) { + handler.post(new Runnable() { + + @Override + public void run() { + callback.onCompletion(); + } + + }); + } + } + + public abstract void execute(PodDBAdapter adapter); + + protected void setResult(Cursor c) { + result = c; + } } } -- cgit v1.2.3 From 71a47c0a5bf99a734081d217eb3e14d75f017a7a Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 1 Jun 2013 18:29:04 +0200 Subject: Ported several classes from FeedManager to DB*-classes --- src/de/danoeh/antennapod/storage/DBReader.java | 41 ++--- src/de/danoeh/antennapod/storage/DBWriter.java | 7 +- .../antennapod/storage/DownloadRequester.java | 2 +- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 177 ++++++++++++++------- 4 files changed, 143 insertions(+), 84 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 5ae47f32b..ab3d6342d 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -78,6 +78,9 @@ public final class DBReader { break; } } + if (item.getFeed() == null) { + Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId()); + } } } @@ -107,7 +110,7 @@ public final class DBReader { private static List extractItemlistFromCursor( PodDBAdapter adapter, Cursor itemlistCursor) { - ArrayList mediaIds = new ArrayList(); + ArrayList itemIds = new ArrayList(); List items = new ArrayList( itemlistCursor.getCount()); @@ -126,12 +129,8 @@ public final class DBReader { .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK)); item.setFeedId(itemlistCursor .getLong(PodDBAdapter.IDX_FI_SMALL_FEED)); - long mediaId = itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_MEDIA); - if (mediaId != 0) { - mediaIds.add(String.valueOf(mediaId)); - item.setMedia(new FeedMedia(mediaId, item)); - } + itemIds.add(String.valueOf(item.getId())); + item.setRead((itemlistCursor .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true : false); @@ -182,23 +181,23 @@ public final class DBReader { } while (itemlistCursor.moveToNext()); } - extractMediafromItemlist(adapter, items, mediaIds); + extractMediafromItemlist(adapter, items, itemIds); Collections.sort(items, new FeedItemPubdateComparator()); return items; } private static void extractMediafromItemlist(PodDBAdapter adapter, - List items, ArrayList mediaIds) { + List items, ArrayList itemIds) { List itemsCopy = new ArrayList(items); - Cursor cursor = adapter.getFeedMediaCursor(mediaIds - .toArray(new String[mediaIds.size()])); + Cursor cursor = adapter.getFeedMediaCursor(itemIds + .toArray(new String[itemIds.size()])); if (cursor.moveToFirst()) { do { long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); // find matching feed item - FeedItem item = getMatchingItemForMedia(mediaId, itemsCopy); - itemsCopy.remove(item); + FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); if (item != null) { Date playbackCompletionDate = null; long playbackCompletionTime = cursor @@ -257,10 +256,10 @@ public final class DBReader { return feed; } - private static FeedItem getMatchingItemForMedia(long mediaId, + private static FeedItem getMatchingItemForMedia(long itemId, List items) { for (FeedItem item : items) { - if (item.getMedia() != null && item.getMedia().getId() == mediaId) { + if (item.getId() == itemId) { return item; } } @@ -355,12 +354,13 @@ public final class DBReader { Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE); String[] itemIds = new String[mediaCursor.getCount()]; - for (int i = 0; i < itemIds.length; i++) { - itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_FEEDITEM_INDEX)); + for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) { + itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX)); } mediaCursor.close(); Cursor itemCursor = adapter.getFeedItemCursor(itemIds); List items = extractItemlistFromCursor(adapter, itemCursor); + loadFeedDataOfFeedItemlist(context, items); itemCursor.close(); adapter.close(); @@ -417,7 +417,9 @@ public final class DBReader { if (feedCursor.moveToFirst()) { feed = extractFeedFromCursorRow(adapter, feedCursor); feed.setItems(getFeedItemList(context, feed)); - } + } else { + Log.e(TAG, "getFeed could not find feed with id " + feedId); + } adapter.close(); return feed; } @@ -432,6 +434,7 @@ public final class DBReader { List list = extractItemlistFromCursor(adapter, itemCursor); if (list.size() > 0) { item = list.get(0); + loadFeedDataOfFeedItemlist(context, list); } } return item; @@ -465,7 +468,7 @@ public final class DBReader { * The id of the object * @return The found object * */ - private static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { + public static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { Cursor cursor = adapter.getImageOfFeedCursor(id); if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { throw new SQLException("No FeedImage found at index: " + id); diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index b5f9acdad..3ff2fddc1 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import android.content.Context; @@ -88,8 +89,8 @@ public class DBWriter { }); } - public static void deleteFeed(final Context context, final long feedId) { - dbExec.submit(new Runnable() { + public static Future deleteFeed(final Context context, final long feedId) { + return dbExec.submit(new Runnable() { @Override public void run() { DownloadRequester requester = DownloadRequester.getInstance(); @@ -378,7 +379,7 @@ public class DBWriter { } - public void moveQueueItem(final Context context, final int from, + public static void moveQueueItem(final Context context, final int from, final int to, final boolean broadcastUpdate) { dbExec.submit(new Runnable() { diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 30abc4491..a27fbf8e4 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -86,7 +86,7 @@ public class DownloadRequester { "Requesting download of url " + item.getDownload_url()); item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); - DownloadRequest request = new DownloadRequest(item.getFile_url(), + DownloadRequest request = new DownloadRequest(dest.toString(), item.getDownload_url(), item.getHumanReadableIdentifier(), item.getId(), item.getTypeAsInt()); diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 6ff22cb55..7c28ade14 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -22,12 +22,14 @@ import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.service.download.DownloadStatus; +// TODO Remove media column from feeditem table + /** * Implements methods for accessing the database * */ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 8; + private static final int DATABASE_VERSION = 9; private static final String DATABASE_NAME = "Antennapod.db"; /** Maximum number of arguments for IN-operator. */ @@ -64,6 +66,7 @@ public class PodDBAdapter { public static final int KEY_SIZE_INDEX = 6; public static final int KEY_MIME_TYPE_INDEX = 7; public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; + public static final int KEY_MEDIA_FEEDITEM_INDEX = 9; // --------- Download log indices public static final int KEY_FEEDFILE_INDEX = 1; public static final int KEY_FEEDFILETYPE_INDEX = 2; @@ -160,7 +163,8 @@ public class PodDBAdapter { + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," - + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER)"; + + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," + + KEY_FEEDITEM + " INTEGER)"; private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE @@ -186,9 +190,16 @@ public class PodDBAdapter { * Select all columns from the feeditems-table except description and * content-encoded. */ - private static final String[] SEL_FI_SMALL = { KEY_ID, KEY_TITLE, - KEY_PUBDATE, KEY_READ, KEY_LINK, KEY_PAYMENT_LINK, KEY_MEDIA, - KEY_FEED, KEY_HAS_CHAPTERS, KEY_ITEM_IDENTIFIER }; + private static final String[] SEL_FI_SMALL = { + TABLE_NAME_FEED_ITEMS + "." + KEY_ID, + TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE, + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE, + TABLE_NAME_FEED_ITEMS + "." + KEY_READ, + TABLE_NAME_FEED_ITEMS + "." + KEY_LINK, + TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER }; // column indices for SEL_FI_SMALL @@ -214,9 +225,18 @@ public class PodDBAdapter { public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2; public static final int IDX_FI_EXTRA_FEED = 3; + static PodDBHelper dbHelperSingleton; + + private static synchronized PodDBHelper getDbHelperSingleton(Context appContext) { + if (dbHelperSingleton == null) { + dbHelperSingleton = new PodDBHelper(appContext, DATABASE_NAME, null, DATABASE_VERSION); + } + return dbHelperSingleton; + } + public PodDBAdapter(Context c) { this.context = c; - helper = new PodDBHelper(context, DATABASE_NAME, null, DATABASE_VERSION); + helper = getDbHelperSingleton(c.getApplicationContext()); } public PodDBAdapter open() { @@ -236,7 +256,7 @@ public class PodDBAdapter { public void close() { if (AppConfig.DEBUG) Log.d(TAG, "Closing DB"); - db.close(); + //db.close(); } /** @@ -319,6 +339,9 @@ public class PodDBAdapter { } else { values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); } + if (media.getItem() != null) { + values.put(KEY_FEEDITEM, media.getItem().getId()); + } if (media.getId() == 0) { media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values)); } else { @@ -376,12 +399,6 @@ public class PodDBAdapter { } values.put(KEY_PUBDATE, item.getPubDate().getTime()); values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); - if (item.getMedia() != null) { - if (item.getMedia().getId() == 0) { - setMedia(item.getMedia()); - } - values.put(KEY_MEDIA, item.getMedia().getId()); - } if (item.getFeed().getId() == 0) { setFeed(item.getFeed()); } @@ -395,6 +412,11 @@ public class PodDBAdapter { db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[] { String.valueOf(item.getId()) }); } + if (item.getMedia() != null) { + if (item.getMedia().getId() == 0) { + setMedia(item.getMedia()); + } + } if (item.getChapters() != null) { setChapters(item); } @@ -662,11 +684,22 @@ public class PodDBAdapter { */ public final Cursor getQueueCursor() { open(); - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, - "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE, - TABLE_NAME_FEED_ITEMS + "." + KEY_ID, - TABLE_NAME_QUEUE + "." + KEY_FEEDITEM }, null, null, - TABLE_NAME_QUEUE + "." + KEY_FEEDITEM); + String selFiSmall = Arrays.toString(SEL_FI_SMALL); + Object[] args = (Object[]) new String[] { + selFiSmall.substring(1, selFiSmall.length() - 1), + TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE, + TABLE_NAME_FEED_ITEMS + "." + KEY_ID, + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM, + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM }; + String query = String.format( + "SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args); + Cursor c = db.rawQuery(query, null); + /* + * Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, + * "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE, + * TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." + + * KEY_FEEDITEM }, null, null, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM); + */ return c; } @@ -715,7 +748,7 @@ public class PodDBAdapter { throw new IllegalArgumentException("Limit must be >= 0"); } open(); - Cursor c = db.query(CREATE_TABLE_FEED_MEDIA, null, + Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_PLAYBACK_COMPLETION_DATE + " IS NOT NULL", null, null, null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit); return c; @@ -746,12 +779,12 @@ public class PodDBAdapter { } cursors[i] = db.rawQuery("SELECT * FROM " - + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_ID + " IN " + + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN " + buildInOperator(neededLength), parts); } return new MergeCursor(cursors); } else { - return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + " IN " + return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN " + buildInOperator(length), mediaIds, null, null, null); } } @@ -781,7 +814,7 @@ public class PodDBAdapter { } open(); - return db.query(TABLE_NAME_FEED_ITEMS, null, KEY_ID + " IN " + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_ID + " IN " + buildInOperator(ids.length), ids, null, null, null); } @@ -883,43 +916,65 @@ public class PodDBAdapter { db.execSQL(CREATE_TABLE_SIMPLECHAPTERS); } - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, - final int newVersion) { - Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " - + newVersion + "."); - if (oldVersion <= 1) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_TYPE + " TEXT"); - } - if (oldVersion <= 2) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_LINK + " TEXT"); - } - if (oldVersion <= 3) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 4) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_FEED_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 5) { - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); - } - if (oldVersion <= 6) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); - } - if (oldVersion <= 7) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE - + " INTEGER"); - } - } - } - + @Override + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, + final int newVersion) { + Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " + + newVersion + "."); + if (oldVersion <= 1) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " + + KEY_TYPE + " TEXT"); + } + if (oldVersion <= 2) { + db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + KEY_LINK + " TEXT"); + } + if (oldVersion <= 3) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 4) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " + + KEY_FEED_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 5) { + db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); + } + if (oldVersion <= 6) { + db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); + } + if (oldVersion <= 7) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE + + " INTEGER"); + } + if (oldVersion <= 8) { + final int KEY_ID_POSITION = 0; + final int KEY_MEDIA_POSITION = 1; + + // Add feeditem column to feedmedia table + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_FEEDITEM + + " INTEGER"); + Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[] {KEY_MEDIA}, null, null, null); + if (feeditemCursor.moveToFirst()) { + db.beginTransaction(); + ContentValues contentValues = new ContentValues(); + do { + long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); + contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); + db.update(TABLE_NAME_FEED_MEDIA, contentValues, "?=?", new String[] {KEY_ID, Long.toString(mediaId)}); + contentValues.clear(); + } while (feeditemCursor.moveToNext()); + db.setTransactionSuccessful(); + db.endTransaction(); + } + feeditemCursor.close(); + } + } + } } -- cgit v1.2.3 From 2071793e6aa1a106744078fcbcf7c0529ed315c4 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Fri, 2 Aug 2013 23:54:50 +0200 Subject: Organizing the queue now works, several bugfixes etc. --- src/de/danoeh/antennapod/storage/DBReader.java | 30 ++++++++-- src/de/danoeh/antennapod/storage/DBTasks.java | 4 +- src/de/danoeh/antennapod/storage/DBWriter.java | 65 +++++++++++++++++----- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 23 +++++++- 4 files changed, 98 insertions(+), 24 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index ab3d6342d..c8135bea1 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -18,7 +18,7 @@ import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.VorbisCommentChapter; -import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.service.download.*; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; @@ -132,8 +132,7 @@ public final class DBReader { itemIds.add(String.valueOf(item.getId())); item.setRead((itemlistCursor - .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true - : false); + .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0)); item.setItemIdentifier(itemlistCursor .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); @@ -182,7 +181,6 @@ public final class DBReader { } extractMediafromItemlist(adapter, items, itemIds); - Collections.sort(items, new FeedItemPubdateComparator()); return items; } @@ -275,11 +273,33 @@ public final class DBReader { itemlistCursor); itemlistCursor.close(); loadFeedDataOfFeedItemlist(context, items); - Collections.sort(items, new FeedItemPubdateComparator()); return items; } + public static List getQueueIDList(Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + + adapter.open(); + List result = getQueueIDList(adapter); + adapter.close(); + + return result; + } + + static List getQueueIDList(PodDBAdapter adapter) { + adapter.open(); + Cursor queueCursor = adapter.getQueueIDCursor(); + + List queueIds = new ArrayList(queueCursor.getCount()); + if (queueCursor.moveToFirst()) { + do { + queueIds.add(queueCursor.getLong(0)); + } while (queueCursor.moveToNext()); + } + return queueIds; + } + public static List getQueue(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting queue"); diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index eceb3c1b4..39c30445e 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -87,7 +87,7 @@ public final class DBTasks { } } - public void refreshExpiredFeeds(final Context context) { + public static void refreshExpiredFeeds(final Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Refreshing expired feeds"); @@ -130,7 +130,7 @@ public final class DBTasks { } /** Updates a specific feed. */ - private static void refreshFeed(Context context, Feed feed) + public static void refreshFeed(Context context, Feed feed) throws DownloadRequestException { DownloadRequester.getInstance().downloadFeed(context, new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 3ff2fddc1..76f6306f8 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.storage; import java.io.File; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -15,13 +16,11 @@ import android.database.Cursor; import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.util.QueueAccess; public class DBWriter { private static final String TAG = "DBWriter"; @@ -349,20 +348,23 @@ public class DBWriter { if (queue != null) { boolean queueModified = false; - - if (itemListContains(queue, itemId)) { + QueueAccess queueAccess = QueueAccess.ItemListAccess(queue); + if (queueAccess.contains(itemId)) { item = DBReader.getFeedItem(context, itemId); if (item != null) { - queue.remove(item); - queueModified = true; + queueModified = queueAccess.remove(itemId); } } if (queueModified) { adapter.setQueue(queue); EventDistributor.getInstance() .sendQueueUpdateBroadcast(); - } - } + } else { + Log.w(TAG, "Queue was not modified by call to removeQueueItem"); + } + } else { + Log.e(TAG, "removeQueueItem: Could not load queue"); + } adapter.close(); if (performAutoDownload) { @@ -393,21 +395,30 @@ public class DBWriter { if (queue != null) { if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) { + final FeedItem item = queue.remove(from); queue.add(to, item); - adapter.setQueue(queue); + + adapter.setQueue(queue); if (broadcastUpdate) { EventDistributor.getInstance() .sendQueueUpdateBroadcast(); } } - } + } else { + Log.e(TAG, "moveQueueItem: Could not load queue"); + } adapter.close(); } }); } + public static void markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) { + long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0; + markItemRead(context, item.getId(), read, mediaId, resetMediaPosition); + } + public static void markItemRead(final Context context, final long itemId, final boolean read) { markItemRead(context, itemId, read, 0, false); @@ -507,9 +518,9 @@ public class DBWriter { } - static void setFeedMedia(final Context context, + public static Future setFeedMedia(final Context context, final FeedMedia media) { - dbExec.submit(new Runnable() { + return dbExec.submit(new Runnable() { @Override public void run() { @@ -522,6 +533,32 @@ public class DBWriter { } + public static Future setFeedItem(final Context context, + final FeedItem item) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setSingleFeedItem(item); + adapter.close(); + }}); + } + + public static Future setFeedImage(final Context context, + final FeedImage image) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setImage(image); + adapter.close(); + }}); + } + private static boolean itemListContains(List items, long itemId) { for (FeedItem item : items) { if (item.getId() == itemId) { diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 7c28ade14..1aa8c93d4 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -305,6 +305,7 @@ public class PodDBAdapter { * @return the id of the entry * */ public long setImage(FeedImage image) { + db.beginTransaction(); ContentValues values = new ContentValues(); values.put(KEY_TITLE, image.getTitle()); values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); @@ -316,6 +317,13 @@ public class PodDBAdapter { db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", new String[] { String.valueOf(image.getId()) }); } + if (image.getFeed() != null && image.getFeed().getId() != 0 ) { + values.clear(); + values.put(KEY_IMAGE, image.getId()); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[] {String.valueOf(image.getFeed().getId())}); + } + db.setTransactionSuccessful(); + db.endTransaction(); return image.getId(); } @@ -686,11 +694,11 @@ public class PodDBAdapter { open(); String selFiSmall = Arrays.toString(SEL_FI_SMALL); Object[] args = (Object[]) new String[] { - selFiSmall.substring(1, selFiSmall.length() - 1), + selFiSmall.substring(1, selFiSmall.length() - 1) + "," + TABLE_NAME_QUEUE + "." + KEY_ID, TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE, TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM, - TABLE_NAME_QUEUE + "." + KEY_FEEDITEM }; + TABLE_NAME_QUEUE + "." + KEY_ID }; String query = String.format( "SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args); Cursor c = db.rawQuery(query, null); @@ -703,6 +711,12 @@ public class PodDBAdapter { return c; } + public Cursor getQueueIDCursor() { + open(); + Cursor c = db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null); + return c; + } + /** * Returns a cursor which contains all feed items in the unread items list. * The returned cursor uses the SEL_FI_SMALL selection. @@ -791,8 +805,11 @@ public class PodDBAdapter { /** Builds an IN-operator argument depending on the number of items. */ private String buildInOperator(int size) { + if (size == 1) { + return "(?)"; + } StringBuffer buffer = new StringBuffer("("); - for (int i = 0; i <= size; i++) { + for (int i = 0; i < size - 1; i++) { buffer.append("?,"); } buffer.append("?)"); -- cgit v1.2.3 From 9ba3dc0d823b786700010a60d38f47c802101d27 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 3 Aug 2013 13:58:31 +0200 Subject: Improved DownloadService, several bugfixes - DownloadService should now terminate properly as soon as all downloads have been completed. - Notification bug ("0 downloads left" notification) should be fixed --- src/de/danoeh/antennapod/storage/DBReader.java | 97 +++- src/de/danoeh/antennapod/storage/DBTasks.java | 23 +- src/de/danoeh/antennapod/storage/DBWriter.java | 8 +- .../antennapod/storage/DownloadRequester.java | 540 +++++++++++---------- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 41 +- 5 files changed, 397 insertions(+), 312 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index c8135bea1..c69607473 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -188,41 +188,45 @@ public final class DBReader { List items, ArrayList itemIds) { List itemsCopy = new ArrayList(items); - Cursor cursor = adapter.getFeedMediaCursor(itemIds - .toArray(new String[itemIds.size()])); + Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds + .toArray(new String[itemIds.size()])); if (cursor.moveToFirst()) { do { - long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); // find matching feed item FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); if (item != null) { - Date playbackCompletionDate = null; - long playbackCompletionTime = cursor - .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); - if (playbackCompletionTime > 0) { - playbackCompletionDate = new Date( - playbackCompletionTime); - } - - item.setMedia(new FeedMedia( - mediaId, - item, - cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), - cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), - cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), - cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), - cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), - cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), - cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, - playbackCompletionDate)); - + item.setMedia(extractFeedMediaFromCursorRow(cursor)); + item.getMedia().setItem(item); } } while (cursor.moveToNext()); cursor.close(); } } + private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) { + long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + Date playbackCompletionDate = null; + long playbackCompletionTime = cursor + .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); + if (playbackCompletionTime > 0) { + playbackCompletionDate = new Date( + playbackCompletionTime); + } + + return new FeedMedia( + mediaId, + null, + cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), + cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), + cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), + cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), + cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), + cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), + cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, + playbackCompletionDate); + } + private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, Cursor cursor) { Date lastUpdate = new Date( @@ -481,6 +485,21 @@ public final class DBReader { 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 Context context, final long imageId) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + FeedImage result = getFeedImage(adapter, imageId); + adapter.close(); + return result; + } + /** * Searches the DB for a FeedImage of the given id. * @@ -488,7 +507,7 @@ public final class DBReader { * The id of the object * @return The found object * */ - public static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { + static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { Cursor cursor = adapter.getImageOfFeedCursor(id); if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { throw new SQLException("No FeedImage found at index: " + id); @@ -504,4 +523,34 @@ public final class DBReader { cursor.close(); return image; } + + /** + * 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 Context context, final long mediaId) { + PodDBAdapter adapter = new PodDBAdapter(context); + + adapter.open(); + Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId); + + FeedMedia media = null; + if (mediaCursor.moveToFirst()) { + final long itemId = mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); + media = extractFeedMediaFromCursorRow(mediaCursor); + FeedItem item = getFeedItem(context, itemId); + if (media != null && item != null) { + media.setItem(item); + item.setMedia(media); + } + } + + mediaCursor.close(); + adapter.close(); + + return media; + } } diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 39c30445e..45ce4298a 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -7,6 +7,7 @@ import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import android.content.Context; @@ -25,6 +26,7 @@ import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.NetworkUtils; +import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; public final class DBTasks { @@ -397,6 +399,11 @@ public final class DBTasks { return result; } + public static boolean isInQueue(Context context, final long feedItemId) { + List queue = DBReader.getQueueIDList(context); + return QueueAccess.IDListAccess(queue).contains(feedItemId); + } + private static Feed searchFeedByIdentifyingValue(Context context, String identifier) { List feeds = DBReader.getFeedList(context); @@ -430,7 +437,13 @@ public final class DBTasks { "Found no existing Feed with title " + newFeed.getTitle() + ". Adding as new one."); // Add a new Feed - DBWriter.addNewFeed(context, newFeed); + try { + DBWriter.addNewFeed(context, newFeed).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } return newFeed; } else { if (AppConfig.DEBUG) @@ -462,7 +475,13 @@ public final class DBTasks { // update attributes savedFeed.setLastUpdate(newFeed.getLastUpdate()); savedFeed.setType(newFeed.getType()); - DBWriter.setCompleteFeed(context, savedFeed); + try { + DBWriter.setCompleteFeed(context, savedFeed).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } new Thread() { @Override public void run() { diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 76f6306f8..a60694f35 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -488,8 +488,8 @@ public class DBWriter { } - static void addNewFeed(final Context context, final Feed feed) { - dbExec.submit(new Runnable() { + static Future addNewFeed(final Context context, final Feed feed) { + return dbExec.submit(new Runnable() { @Override public void run() { @@ -503,8 +503,8 @@ public class DBWriter { }); } - static void setCompleteFeed(final Context context, final Feed feed) { - dbExec.submit(new Runnable() { + static Future setCompleteFeed(final Context context, final Feed feed) { + return dbExec.submit(new Runnable() { @Override public void run() { diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index a27fbf8e4..4e74d5e98 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -24,273 +24,277 @@ import de.danoeh.antennapod.util.FileNameGenerator; import de.danoeh.antennapod.util.URLChecker; public class DownloadRequester { - private static final String TAG = "DownloadRequester"; - - public static String IMAGE_DOWNLOADPATH = "images/"; - public static String FEED_DOWNLOADPATH = "cache/"; - public static String MEDIA_DOWNLOADPATH = "media/"; - - private static DownloadRequester downloader; - - Map downloads; - - private DownloadRequester() { - downloads = new ConcurrentHashMap(); - } - - public static DownloadRequester getInstance() { - if (downloader == null) { - downloader = new DownloadRequester(); - } - return downloader; - } - - private void download(Context context, FeedFile item, File dest, - boolean overwriteIfExists) { - if (!isDownloadingFile(item)) { - if (!isFilenameAvailable(dest.toString()) || dest.exists()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Filename already used."); - if (isFilenameAvailable(dest.toString()) && overwriteIfExists) { - boolean result = dest.delete(); - if (AppConfig.DEBUG) - Log.d(TAG, "Deleting file. Result: " + result); - } else { - // find different name - File newDest = null; - for (int i = 1; i < Integer.MAX_VALUE; i++) { - String newName = FilenameUtils.getBaseName(dest - .getName()) - + "-" - + i - + "." - + FilenameUtils.getExtension(dest.getName()); - if (AppConfig.DEBUG) - Log.d(TAG, "Testing filename " + newName); - newDest = new File(dest.getParent(), newName); - if (!newDest.exists() - && isFilenameAvailable(newDest.toString())) { - if (AppConfig.DEBUG) - Log.d(TAG, "File doesn't exist yet. Using " - + newName); - break; - } - } - if (newDest != null) { - dest = newDest; - } - } - } - if (AppConfig.DEBUG) - Log.d(TAG, - "Requesting download of url " + item.getDownload_url()); - item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); - - DownloadRequest request = new DownloadRequest(dest.toString(), - item.getDownload_url(), item.getHumanReadableIdentifier(), - item.getId(), item.getTypeAsInt()); - - downloads.put(request.getSource(), request); - - - if (!DownloadService.isRunning) { - Intent launchIntent = new Intent(context, DownloadService.class); - launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); - context.startService(launchIntent); - } else { - Intent queueIntent = new Intent( - DownloadService.ACTION_ENQUEUE_DOWNLOAD); - queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request); - context.sendBroadcast(queueIntent); - } - EventDistributor.getInstance().sendDownloadQueuedBroadcast(); - } else { - Log.e(TAG, "URL " + item.getDownload_url() - + " is already being downloaded"); - } - } - - /** - * Returns true if a filename is available and false if it has already been - * taken by another requested download. - */ - private boolean isFilenameAvailable(String path) { - for (String key : downloads.keySet()) { - DownloadRequest r = downloads.get(key); - if (StringUtils.equals(r.getDestination(), path)) { - if (AppConfig.DEBUG) - Log.d(TAG, path - + " is already used by another requested download"); - return false; - } - } - if (AppConfig.DEBUG) - Log.d(TAG, path + " is available as a download destination"); - return true; - } - - public void downloadFeed(Context context, Feed feed) - throws DownloadRequestException { - if (feedFileValid(feed)) { - download(context, feed, new File(getFeedfilePath(context), - getFeedfileName(feed)), true); - } - } - - public void downloadImage(Context context, FeedImage image) - throws DownloadRequestException { - if (feedFileValid(image)) { - download(context, image, new File(getImagefilePath(context), - getImagefileName(image)), true); - } - } - - public void downloadMedia(Context context, FeedMedia feedmedia) - throws DownloadRequestException { - if (feedFileValid(feedmedia)) { - download(context, feedmedia, - new File(getMediafilePath(context, feedmedia), - getMediafilename(feedmedia)), false); - } - } - - /** - * Throws a DownloadRequestException if the feedfile or the download url of - * the feedfile is null. - * - * @throws DownloadRequestException - */ - private boolean feedFileValid(FeedFile f) throws DownloadRequestException { - if (f == null) { - throw new DownloadRequestException("Feedfile was null"); - } else if (f.getDownload_url() == null) { - throw new DownloadRequestException("File has no download URL"); - } else { - return true; - } - } - - /** - * Cancels a running download. - * */ - public void cancelDownload(final Context context, final FeedFile f) { - cancelDownload(context, f.getDownload_url()); - } - - /** - * Cancels a running download. - * */ - public void cancelDownload(final Context context, final String downloadUrl) { - if (AppConfig.DEBUG) - Log.d(TAG, "Cancelling download with url " + downloadUrl); - Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD); - cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl); - context.sendBroadcast(cancelIntent); - } - - /** Cancels all running downloads */ - public void cancelAllDownloads(Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Cancelling all running downloads"); - context.sendBroadcast(new Intent( - DownloadService.ACTION_CANCEL_ALL_DOWNLOADS)); - } - - /** Returns true if there is at least one Feed in the downloads queue. */ - public boolean isDownloadingFeeds() { - for (DownloadRequest r : downloads.values()) { - if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { - return true; - } - } - return false; - } - - /** Checks if feedfile is in the downloads list */ - public boolean isDownloadingFile(FeedFile item) { - if (item.getDownload_url() != null) { - return downloads.containsKey(item.getDownload_url()); - } - return false; - } - - public DownloadRequest getDownload(String downloadUrl) { - return downloads.get(downloadUrl); - } - - /** Checks if feedfile with the given download url is in the downloads list */ - public boolean isDownloadingFile(String downloadUrl) { - return downloads.get(downloadUrl) != null; - } - - public boolean hasNoDownloads() { - return downloads.isEmpty(); - } - - /** Remove an object from the downloads-list of the requester. */ - public void removeDownload(DownloadRequest r) { - if (downloads.remove(r.getSource()) == null) { - Log.e(TAG, - "Could not remove object with url " + r.getSource()); - } - } - - /** Get the number of uncompleted Downloads */ - public int getNumberOfDownloads() { - return downloads.size(); - } - - public String getFeedfilePath(Context context) - throws DownloadRequestException { - return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH) - .toString() + "/"; - } - - public String getFeedfileName(Feed feed) { - String filename = feed.getDownload_url(); - if (feed.getTitle() != null && !feed.getTitle().isEmpty()) { - filename = feed.getTitle(); - } - return "feed-" + FileNameGenerator.generateFileName(filename); - } - - public String getImagefilePath(Context context) - throws DownloadRequestException { - return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH) - .toString() + "/"; - } - - public String getImagefileName(FeedImage image) { - String filename = image.getDownload_url(); - if (image.getFeed() != null && image.getFeed().getTitle() != null) { - filename = image.getFeed().getTitle(); - } - return "image-" + FileNameGenerator.generateFileName(filename); - } - - public String getMediafilePath(Context context, FeedMedia media) - throws DownloadRequestException { - File externalStorage = getExternalFilesDirOrThrowException( - context, - MEDIA_DOWNLOADPATH - + FileNameGenerator.generateFileName(media.getItem() - .getFeed().getTitle()) + "/"); - return externalStorage.toString(); - } - - private File getExternalFilesDirOrThrowException(Context context, - String type) throws DownloadRequestException { - File result = UserPreferences.getDataFolder(context, type); - if (result == null) { - throw new DownloadRequestException( - "Failed to access external storage"); - } - return result; - } - - public String getMediafilename(FeedMedia media) { - return URLUtil.guessFileName(media.getDownload_url(), null, - media.getMime_type()); - } + private static final String TAG = "DownloadRequester"; + + public static String IMAGE_DOWNLOADPATH = "images/"; + public static String FEED_DOWNLOADPATH = "cache/"; + public static String MEDIA_DOWNLOADPATH = "media/"; + + private static DownloadRequester downloader; + + Map downloads; + + private DownloadRequester() { + downloads = new ConcurrentHashMap(); + } + + public static DownloadRequester getInstance() { + if (downloader == null) { + downloader = new DownloadRequester(); + } + return downloader; + } + + private void download(Context context, FeedFile item, File dest, + boolean overwriteIfExists) { + if (!isDownloadingFile(item)) { + if (!isFilenameAvailable(dest.toString()) || dest.exists()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Filename already used."); + if (isFilenameAvailable(dest.toString()) && overwriteIfExists) { + boolean result = dest.delete(); + if (AppConfig.DEBUG) + Log.d(TAG, "Deleting file. Result: " + result); + } else { + // find different name + File newDest = null; + for (int i = 1; i < Integer.MAX_VALUE; i++) { + String newName = FilenameUtils.getBaseName(dest + .getName()) + + "-" + + i + + "." + + FilenameUtils.getExtension(dest.getName()); + if (AppConfig.DEBUG) + Log.d(TAG, "Testing filename " + newName); + newDest = new File(dest.getParent(), newName); + if (!newDest.exists() + && isFilenameAvailable(newDest.toString())) { + if (AppConfig.DEBUG) + Log.d(TAG, "File doesn't exist yet. Using " + + newName); + break; + } + } + if (newDest != null) { + dest = newDest; + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, + "Requesting download of url " + item.getDownload_url()); + item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); + + DownloadRequest request = new DownloadRequest(dest.toString(), + item.getDownload_url(), item.getHumanReadableIdentifier(), + item.getId(), item.getTypeAsInt()); + + downloads.put(request.getSource(), request); + + Intent launchIntent = new Intent(context, DownloadService.class); + launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); + context.startService(launchIntent); + EventDistributor.getInstance().sendDownloadQueuedBroadcast(); + } else { + Log.e(TAG, "URL " + item.getDownload_url() + + " is already being downloaded"); + } + } + + /** + * Returns true if a filename is available and false if it has already been + * taken by another requested download. + */ + private boolean isFilenameAvailable(String path) { + for (String key : downloads.keySet()) { + DownloadRequest r = downloads.get(key); + if (StringUtils.equals(r.getDestination(), path)) { + if (AppConfig.DEBUG) + Log.d(TAG, path + + " is already used by another requested download"); + return false; + } + } + if (AppConfig.DEBUG) + Log.d(TAG, path + " is available as a download destination"); + return true; + } + + public void downloadFeed(Context context, Feed feed) + throws DownloadRequestException { + if (feedFileValid(feed)) { + download(context, feed, new File(getFeedfilePath(context), + getFeedfileName(feed)), true); + } + } + + public void downloadImage(Context context, FeedImage image) + throws DownloadRequestException { + if (feedFileValid(image)) { + download(context, image, new File(getImagefilePath(context), + getImagefileName(image)), true); + } + } + + public void downloadMedia(Context context, FeedMedia feedmedia) + throws DownloadRequestException { + if (feedFileValid(feedmedia)) { + download(context, feedmedia, + new File(getMediafilePath(context, feedmedia), + getMediafilename(feedmedia)), false); + } + } + + /** + * Throws a DownloadRequestException if the feedfile or the download url of + * the feedfile is null. + * + * @throws DownloadRequestException + */ + private boolean feedFileValid(FeedFile f) throws DownloadRequestException { + if (f == null) { + throw new DownloadRequestException("Feedfile was null"); + } else if (f.getDownload_url() == null) { + throw new DownloadRequestException("File has no download URL"); + } else { + return true; + } + } + + /** + * Cancels a running download. + */ + public void cancelDownload(final Context context, final FeedFile f) { + cancelDownload(context, f.getDownload_url()); + } + + /** + * Cancels a running download. + */ + public void cancelDownload(final Context context, final String downloadUrl) { + if (AppConfig.DEBUG) + Log.d(TAG, "Cancelling download with url " + downloadUrl); + Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD); + cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl); + context.sendBroadcast(cancelIntent); + } + + /** + * Cancels all running downloads + */ + public void cancelAllDownloads(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Cancelling all running downloads"); + context.sendBroadcast(new Intent( + DownloadService.ACTION_CANCEL_ALL_DOWNLOADS)); + } + + /** + * Returns true if there is at least one Feed in the downloads queue. + */ + public boolean isDownloadingFeeds() { + for (DownloadRequest r : downloads.values()) { + if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { + return true; + } + } + return false; + } + + /** + * Checks if feedfile is in the downloads list + */ + public boolean isDownloadingFile(FeedFile item) { + if (item.getDownload_url() != null) { + return downloads.containsKey(item.getDownload_url()); + } + return false; + } + + public DownloadRequest getDownload(String downloadUrl) { + return downloads.get(downloadUrl); + } + + /** + * Checks if feedfile with the given download url is in the downloads list + */ + public boolean isDownloadingFile(String downloadUrl) { + return downloads.get(downloadUrl) != null; + } + + public boolean hasNoDownloads() { + return downloads.isEmpty(); + } + + /** + * Remove an object from the downloads-list of the requester. + */ + public void removeDownload(DownloadRequest r) { + if (downloads.remove(r.getSource()) == null) { + Log.e(TAG, + "Could not remove object with url " + r.getSource()); + } + } + + /** + * Get the number of uncompleted Downloads + */ + public int getNumberOfDownloads() { + return downloads.size(); + } + + public String getFeedfilePath(Context context) + throws DownloadRequestException { + return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH) + .toString() + "/"; + } + + public String getFeedfileName(Feed feed) { + String filename = feed.getDownload_url(); + if (feed.getTitle() != null && !feed.getTitle().isEmpty()) { + filename = feed.getTitle(); + } + return "feed-" + FileNameGenerator.generateFileName(filename); + } + + public String getImagefilePath(Context context) + throws DownloadRequestException { + return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH) + .toString() + "/"; + } + + public String getImagefileName(FeedImage image) { + String filename = image.getDownload_url(); + if (image.getFeed() != null && image.getFeed().getTitle() != null) { + filename = image.getFeed().getTitle(); + } + return "image-" + FileNameGenerator.generateFileName(filename); + } + + public String getMediafilePath(Context context, FeedMedia media) + throws DownloadRequestException { + File externalStorage = getExternalFilesDirOrThrowException( + context, + MEDIA_DOWNLOADPATH + + FileNameGenerator.generateFileName(media.getItem() + .getFeed().getTitle()) + "/"); + return externalStorage.toString(); + } + + private File getExternalFilesDirOrThrowException(Context context, + String type) throws DownloadRequestException { + File result = UserPreferences.getDataFolder(context, type); + if (result == null) { + throw new DownloadRequestException( + "Failed to access external storage"); + } + return result; + } + + public String getMediafilename(FeedMedia media) { + return URLUtil.guessFileName(media.getDownload_url(), null, + media.getMime_type()); + } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 1aa8c93d4..14ef07c34 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -201,6 +201,13 @@ public class PodDBAdapter { TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER }; + /** Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries. */ + private static final String SEL_FI_SMALL_STR; + static { + String selFiSmall = Arrays.toString(SEL_FI_SMALL); + SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1); + } + // column indices for SEL_FI_SMALL public static final int IDX_FI_SMALL_ID = 0; @@ -601,7 +608,7 @@ public class PodDBAdapter { public final Cursor getAllFeedsCursor() { open(); Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null, - null); + KEY_TITLE + " ASC"); return c; } @@ -692,9 +699,8 @@ public class PodDBAdapter { */ public final Cursor getQueueCursor() { open(); - String selFiSmall = Arrays.toString(SEL_FI_SMALL); Object[] args = (Object[]) new String[] { - selFiSmall.substring(1, selFiSmall.length() - 1) + "," + TABLE_NAME_QUEUE + "." + KEY_ID, + SEL_FI_SMALL_STR + "," + TABLE_NAME_QUEUE + "." + KEY_ID, TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE, TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM, @@ -738,12 +744,12 @@ public class PodDBAdapter { public Cursor getDownloadedItemsCursor() { open(); - Cursor c = db.rawQuery("SELECT ? FROM " + TABLE_NAME_FEED_ITEMS - + "FULL JOIN " + TABLE_NAME_FEED_MEDIA + " ON " - + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" - + TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " WHERE " - + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0", - SEL_FI_SMALL); + 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_ID + " WHERE " + + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0"; + Cursor c = db.rawQuery(query, null); return c; } @@ -768,7 +774,11 @@ public class PodDBAdapter { return c; } - public final Cursor getFeedMediaCursor(String... mediaIds) { + public final Cursor getSingleFeedMediaCursor(long id) { + return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[] {Long.toString(id)}, null, null, null); + } + + public final Cursor getFeedMediaCursorByItemID(String... mediaIds) { int length = mediaIds.length; if (length > IN_OPERATOR_MAXIMUM) { Log.w(TAG, "Length of id array is larger than " @@ -837,11 +847,14 @@ public class PodDBAdapter { } public final int getNumberOfDownloadedEpisodes() { + final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA + + " WHERE " + KEY_DOWNLOADED + " > 0"; - Cursor c = db.rawQuery( - "SELECT COUNT(DISTINCT ?) AS count FROM ? WHERE ?>0", - new String[] { KEY_ID, TABLE_NAME_FEED_MEDIA, KEY_DOWNLOADED }); - final int result = c.getInt(0); + Cursor c = db.rawQuery(query, null); + int result = 0; + if (c.moveToFirst()) { + result = c.getInt(0); + } c.close(); return result; } -- cgit v1.2.3 From e28229a29c1f60657c82f78507120b215167f039 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 4 Aug 2013 15:35:18 +0200 Subject: Ported playback service to DB* classes --- src/de/danoeh/antennapod/storage/DBTasks.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 45ce4298a..5853bbb02 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -381,9 +381,11 @@ public final class DBTasks { } public static FeedItem getQueueSuccessorOfItem(Context context, - final long itemId) { + final long itemId, List queue) { FeedItem result = null; - List queue = DBReader.getQueue(context); + if (queue == null) { + queue = DBReader.getQueue(context); + } if (queue != null) { Iterator iterator = queue.iterator(); while (iterator.hasNext()) { -- cgit v1.2.3 From 355fc8114f61ed2ecde8f118c4d30d209ceb6198 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 4 Aug 2013 17:28:29 +0200 Subject: Ported playback classes to DB* classes --- src/de/danoeh/antennapod/storage/DBReader.java | 15 +++++++++++++++ src/de/danoeh/antennapod/storage/DBWriter.java | 14 ++++++++++++-- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 11 +++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index c69607473..bf81ad7d4 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -476,6 +476,21 @@ public final class DBReader { return item; } + + public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor extraCursor = adapter.getExtraInformationOfItem(item); + if (extraCursor.moveToFirst()) { + String description = extraCursor + .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION); + String contentEncoded = extraCursor + .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED); + item.setDescription(description); + item.setContentEncoded(contentEncoded); + } + adapter.close(); + } public static int getNumberOfDownloadedEpisodes(final Context context) { PodDBAdapter adapter = new PodDBAdapter(context); diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index a60694f35..3db0b8a33 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -529,10 +529,20 @@ public class DBWriter { adapter.setMedia(media); adapter.close(); }}); - - } + public static Future setFeedMediaPosition(final Context context, final FeedMedia media) { + return dbExec.submit(new Runnable(){ + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedMediaPosition(media); + adapter.close(); + } + }); + } + public static Future setFeedItem(final Context context, final FeedItem item) { return dbExec.submit(new Runnable() { diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 14ef07c34..4ef76fcd6 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -366,6 +366,17 @@ public class PodDBAdapter { return media.getId(); } + public void setFeedMediaPosition(FeedMedia media) { + if (media.getId() != 0) { + ContentValues values = new ContentValues(); + values.put(KEY_POSITION, media.getPosition()); + db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", + new String[] { String.valueOf(media.getId()) }); + } else { + Log.e(TAG, "setFeedMediaPosition: ID of media was 0"); + } + } + /** * Insert all FeedItems of a feed and the feed object itself in a single * transaction -- cgit v1.2.3 From 24c50f7840ffd6af0ff16aa1e73f43613696d637 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 4 Aug 2013 21:35:44 +0200 Subject: Ported search components to DB*-classes --- src/de/danoeh/antennapod/storage/DBReader.java | 16 + src/de/danoeh/antennapod/storage/DBTasks.java | 1166 ++++++++++---------- src/de/danoeh/antennapod/storage/FeedSearcher.java | 281 +++++ src/de/danoeh/antennapod/storage/PodDBAdapter.java | 69 +- 4 files changed, 926 insertions(+), 606 deletions(-) create mode 100644 src/de/danoeh/antennapod/storage/FeedSearcher.java (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index bf81ad7d4..5d6dd9756 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -108,6 +108,14 @@ public final class DBReader { return items; } + static List extractItemlistFromCursor(Context context, Cursor itemlistCursor) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List result = extractItemlistFromCursor(adapter, itemlistCursor); + adapter.close(); + return result; + } + private static List extractItemlistFromCursor( PodDBAdapter adapter, Cursor itemlistCursor) { ArrayList itemIds = new ArrayList(); @@ -500,6 +508,14 @@ public final class DBReader { return result; } + public static int getNumberOfUnreadItems(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final int result = adapter.getNumberOfUnreadItems(); + adapter.close(); + return result; + } + /** * Searches the DB for a FeedImage of the given id. * diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 5853bbb02..bf615776d 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -7,7 +7,9 @@ import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; import java.util.concurrent.locks.ReentrantLock; import android.content.Context; @@ -30,621 +32,593 @@ import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; public final class DBTasks { - private static final String TAG = "DBTasks"; - - private DBTasks() { - } - - public static void playMedia(final Context context, final FeedMedia media, - boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { - try { - if (!shouldStream) { - if (media.fileExists() == false) { - throw new MediaFileNotFoundException( - "No episode was found at " + media.getFile_url(), - media); - } - } - // Start playback Service - Intent launchIntent = new Intent(context, PlaybackService.class); - launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); - launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, - startWhenPrepared); - launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, - shouldStream); - launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, - true); - context.startService(launchIntent); - if (showPlayer) { - // Launch Mediaplayer - context.startActivity(PlaybackService.getPlayerActivityIntent( - context, media)); - } - DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false); - } catch (MediaFileNotFoundException e) { - e.printStackTrace(); - if (media.isPlaying()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - notifyMissingFeedMediaFile(context, media); - } - } - - private static ReentrantLock refreshAllFeedsLock = new ReentrantLock(); - - public static void refreshAllFeeds(final Context context, - final List feeds) { - if (refreshAllFeedsLock.tryLock()) { - new Thread() { - public void run() { - refreshFeeds(context, feeds); - refreshAllFeedsLock.unlock(); - } - }.start(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, - "Ignoring request to refresh all feeds: Refresh lock is locked"); - } - } - - public static void refreshExpiredFeeds(final Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Refreshing expired feeds"); - - new Thread() { - public void run() { - long millis = UserPreferences.getUpdateInterval(); - - if (millis > 0) { - long now = Calendar.getInstance().getTime().getTime(); - - // Allow a 10 minute window - millis -= 10 * 60 * 1000; - List feedList = DBReader.getExpiredFeedsList(context, - now - millis); - if (feedList.size() > 0) { - refreshFeeds(context, feedList); - } - } - } - }.start(); - } - - private static void refreshFeeds(final Context context, - final List feedList) { - - for (Feed feed : feedList) { - try { - refreshFeed(context, feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - context, - new DownloadStatus(feed, feed - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, false, e - .getMessage())); - } - } - - } - - /** Updates a specific feed. */ - public static void refreshFeed(Context context, Feed feed) - throws DownloadRequestException { - DownloadRequester.getInstance().downloadFeed(context, - new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); - } - - public static void notifyInvalidImageFile(final Context context, - final FeedImage image) { - Log.i(TAG, - "The feedmanager was notified about an invalid image download. It will now try to redownload the image file"); - try { - DownloadRequester.getInstance().downloadImage(context, image); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Log.w(TAG, "Failed to download invalid feed image"); - } - } - - public static void notifyMissingFeedMediaFile(final Context context, - final FeedMedia media) { - Log.i(TAG, - "The feedmanager was notified about a missing episode. It will update its database now."); - media.setDownloaded(false); - media.setFile_url(null); - DBWriter.setFeedMedia(context, media); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - - public static void downloadAllItemsInQueue(final Context context) { - new Thread() { - public void run() { - List queue = DBReader.getQueue(context); - if (!queue.isEmpty()) { - try { - downloadFeedItems(context, - queue.toArray(new FeedItem[queue.size()])); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } - } - }.start(); - } - - public static void downloadFeedItems(final Context context, - FeedItem... items) throws DownloadRequestException { - downloadFeedItems(true, context, items); - } - - private static void downloadFeedItems(boolean performAutoCleanup, - final Context context, final FeedItem... items) - throws DownloadRequestException { - final DownloadRequester requester = DownloadRequester.getInstance(); - - if (performAutoCleanup) { - new Thread() { - - @Override - public void run() { - performAutoCleanup(context, - getPerformAutoCleanupArgs(context, items.length)); - } - - }.start(); - } - for (FeedItem item : items) { - if (item.getMedia() != null - && !requester.isDownloadingFile(item.getMedia()) - && !item.getMedia().isDownloaded()) { - if (items.length > 1) { - try { - requester.downloadMedia(context, item.getMedia()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus(context, - new DownloadStatus(item.getMedia(), item - .getMedia() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage())); - } - } else { - requester.downloadMedia(context, item.getMedia()); - } - } - } - } - - private static int getNumberOfUndownloadedEpisodes( - final List queue, final List unreadItems) { - int counter = 0; - for (FeedItem item : queue) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying()) { - counter++; - } - } - for (FeedItem item : unreadItems) { - if (item.hasMedia() && !item.getMedia().isDownloaded()) { - counter++; - } - } - return counter; - } - - public static void autodownloadUndownloadedItems(final Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Performing auto-dl of undownloaded episodes"); - if (NetworkUtils.autodownloadNetworkAvailable(context) - && UserPreferences.isEnableAutodownload()) { - final List queue = DBReader.getQueue(context); - final List unreadItems = DBReader - .getUnreadItemsList(context); - - int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue, - unreadItems); - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(context); - int deletedEpisodes = performAutoCleanup(context, - getPerformAutoCleanupArgs(context, undownloadedEpisodes)); - int episodeSpaceLeft = undownloadedEpisodes; - boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences - .getEpisodeCacheSizeUnlimited(); - - if (!cacheIsUnlimited - && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes - + undownloadedEpisodes) { - episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() - - (downloadedEpisodes - deletedEpisodes); - } - - List itemsToDownload = new ArrayList(); - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (int i = 0; i < queue.size(); i++) { // ignore playing item - FeedItem item = queue.get(i); - if (item.hasMedia() && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (FeedItem item : unreadItems) { - if (item.hasMedia() && !item.getMedia().isDownloaded()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - if (AppConfig.DEBUG) - Log.d(TAG, "Enqueueing " + itemsToDownload.size() - + " items for download"); - - try { - downloadFeedItems(false, context, - itemsToDownload.toArray(new FeedItem[itemsToDownload - .size()])); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - - } - } - - private static int getPerformAutoCleanupArgs(Context context, - final int episodeNumber) { - if (episodeNumber >= 0 - && UserPreferences.getEpisodeCacheSize() != UserPreferences - .getEpisodeCacheSizeUnlimited()) { - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(context); - if (downloadedEpisodes + episodeNumber >= UserPreferences - .getEpisodeCacheSize()) { - - return downloadedEpisodes + episodeNumber - - UserPreferences.getEpisodeCacheSize(); - } - } - return 0; - } - - public static void performAutoCleanup(final Context context) { - performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); - } - - private static int performAutoCleanup(final Context context, - final int episodeNumber) { - List candidates = DBReader.getDownloadedItems(context); - List queue = DBReader.getQueue(context); - List delete; - for (FeedItem item : candidates) { - if (item.hasMedia() && item.getMedia().isDownloaded() - && !queue.contains(item) && item.isRead()) { - candidates.add(item); - } - - } - - Collections.sort(candidates, new Comparator() { - @Override - public int compare(FeedItem lhs, FeedItem rhs) { - Date l = lhs.getMedia().getPlaybackCompletionDate(); - Date r = rhs.getMedia().getPlaybackCompletionDate(); - - if (l == null) { - l = new Date(0); - } - if (r == null) { - r = new Date(0); - } - return l.compareTo(r); - } - }); - - if (candidates.size() > episodeNumber) { - delete = candidates.subList(0, episodeNumber); - } else { - delete = candidates; - } - - for (FeedItem item : delete) { - DBWriter.deleteFeedMediaOfItem(context, item.getId()); - } - - int counter = delete.size(); - - if (AppConfig.DEBUG) - Log.d(TAG, String.format( - "Auto-delete deleted %d episodes (%d requested)", counter, - episodeNumber)); - - return counter; - } - - public static void enqueueAllNewItems(final Context context) { - long[] unreadItems = DBReader.getUnreadItemIds(context); - DBWriter.addQueueItem(context, unreadItems); - } - - public static FeedItem getQueueSuccessorOfItem(Context context, - final long itemId, List queue) { - FeedItem result = null; + private static final String TAG = "DBTasks"; + + private DBTasks() { + } + + public static void playMedia(final Context context, final FeedMedia media, + boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { + try { + if (!shouldStream) { + if (media.fileExists() == false) { + throw new MediaFileNotFoundException( + "No episode was found at " + media.getFile_url(), + media); + } + } + // Start playback Service + Intent launchIntent = new Intent(context, PlaybackService.class); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); + launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, + startWhenPrepared); + launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, + shouldStream); + launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, + true); + context.startService(launchIntent); + if (showPlayer) { + // Launch Mediaplayer + context.startActivity(PlaybackService.getPlayerActivityIntent( + context, media)); + } + DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false); + } catch (MediaFileNotFoundException e) { + e.printStackTrace(); + if (media.isPlaying()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + notifyMissingFeedMediaFile(context, media); + } + } + + private static ReentrantLock refreshAllFeedsLock = new ReentrantLock(); + + public static void refreshAllFeeds(final Context context, + final List feeds) { + if (refreshAllFeedsLock.tryLock()) { + new Thread() { + public void run() { + if (feeds != null) { + refreshFeeds(context, feeds); + } else { + refreshFeeds(context, DBReader.getFeedList(context)); + } + refreshAllFeedsLock.unlock(); + } + }.start(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, + "Ignoring request to refresh all feeds: Refresh lock is locked"); + } + } + + public static void refreshExpiredFeeds(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Refreshing expired feeds"); + + new Thread() { + public void run() { + long millis = UserPreferences.getUpdateInterval(); + + if (millis > 0) { + long now = Calendar.getInstance().getTime().getTime(); + + // Allow a 10 minute window + millis -= 10 * 60 * 1000; + List feedList = DBReader.getExpiredFeedsList(context, + now - millis); + if (feedList.size() > 0) { + refreshFeeds(context, feedList); + } + } + } + }.start(); + } + + private static void refreshFeeds(final Context context, + final List feedList) { + + for (Feed feed : feedList) { + try { + refreshFeed(context, feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus( + context, + new DownloadStatus(feed, feed + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, false, e + .getMessage())); + } + } + + } + + /** + * Updates a specific feed. + */ + public static void refreshFeed(Context context, Feed feed) + throws DownloadRequestException { + DownloadRequester.getInstance().downloadFeed(context, + new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); + } + + public static void notifyInvalidImageFile(final Context context, + final FeedImage image) { + Log.i(TAG, + "The feedmanager was notified about an invalid image download. It will now try to redownload the image file"); + try { + DownloadRequester.getInstance().downloadImage(context, image); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Log.w(TAG, "Failed to download invalid feed image"); + } + } + + public static void notifyMissingFeedMediaFile(final Context context, + final FeedMedia media) { + Log.i(TAG, + "The feedmanager was notified about a missing episode. It will update its database now."); + media.setDownloaded(false); + media.setFile_url(null); + DBWriter.setFeedMedia(context, media); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + + public static void downloadAllItemsInQueue(final Context context) { + new Thread() { + public void run() { + List queue = DBReader.getQueue(context); + if (!queue.isEmpty()) { + try { + downloadFeedItems(context, + queue.toArray(new FeedItem[queue.size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + } + } + }.start(); + } + + public static void downloadFeedItems(final Context context, + FeedItem... items) throws DownloadRequestException { + downloadFeedItems(true, context, items); + } + + private static void downloadFeedItems(boolean performAutoCleanup, + final Context context, final FeedItem... items) + throws DownloadRequestException { + final DownloadRequester requester = DownloadRequester.getInstance(); + + if (performAutoCleanup) { + new Thread() { + + @Override + public void run() { + performAutoCleanup(context, + getPerformAutoCleanupArgs(context, items.length)); + } + + }.start(); + } + for (FeedItem item : items) { + if (item.getMedia() != null + && !requester.isDownloadingFile(item.getMedia()) + && !item.getMedia().isDownloaded()) { + if (items.length > 1) { + try { + requester.downloadMedia(context, item.getMedia()); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus(context, + new DownloadStatus(item.getMedia(), item + .getMedia() + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, + false, e.getMessage())); + } + } else { + requester.downloadMedia(context, item.getMedia()); + } + } + } + } + + private static int getNumberOfUndownloadedEpisodes( + final List queue, final List unreadItems) { + int counter = 0; + for (FeedItem item : queue) { + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + counter++; + } + } + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + counter++; + } + } + return counter; + } + + public static void autodownloadUndownloadedItems(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Performing auto-dl of undownloaded episodes"); + if (NetworkUtils.autodownloadNetworkAvailable(context) + && UserPreferences.isEnableAutodownload()) { + final List queue = DBReader.getQueue(context); + final List unreadItems = DBReader + .getUnreadItemsList(context); + + int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue, + unreadItems); + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(context); + int deletedEpisodes = performAutoCleanup(context, + getPerformAutoCleanupArgs(context, undownloadedEpisodes)); + int episodeSpaceLeft = undownloadedEpisodes; + boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences + .getEpisodeCacheSizeUnlimited(); + + if (!cacheIsUnlimited + && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes + + undownloadedEpisodes) { + episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() + - (downloadedEpisodes - deletedEpisodes); + } + + List itemsToDownload = new ArrayList(); + if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { + for (int i = 0; i < queue.size(); i++) { // ignore playing item + FeedItem item = queue.get(i); + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Enqueueing " + itemsToDownload.size() + + " items for download"); + + try { + downloadFeedItems(false, context, + itemsToDownload.toArray(new FeedItem[itemsToDownload + .size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + + } + } + + private static int getPerformAutoCleanupArgs(Context context, + final int episodeNumber) { + if (episodeNumber >= 0 + && UserPreferences.getEpisodeCacheSize() != UserPreferences + .getEpisodeCacheSizeUnlimited()) { + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(context); + if (downloadedEpisodes + episodeNumber >= UserPreferences + .getEpisodeCacheSize()) { + + return downloadedEpisodes + episodeNumber + - UserPreferences.getEpisodeCacheSize(); + } + } + return 0; + } + + public static void performAutoCleanup(final Context context) { + performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); + } + + private static int performAutoCleanup(final Context context, + final int episodeNumber) { + List candidates = DBReader.getDownloadedItems(context); + List queue = DBReader.getQueue(context); + List delete; + for (FeedItem item : candidates) { + if (item.hasMedia() && item.getMedia().isDownloaded() + && !queue.contains(item) && item.isRead()) { + candidates.add(item); + } + + } + + Collections.sort(candidates, new Comparator() { + @Override + public int compare(FeedItem lhs, FeedItem rhs) { + Date l = lhs.getMedia().getPlaybackCompletionDate(); + Date r = rhs.getMedia().getPlaybackCompletionDate(); + + if (l == null) { + l = new Date(0); + } + if (r == null) { + r = new Date(0); + } + return l.compareTo(r); + } + }); + + if (candidates.size() > episodeNumber) { + delete = candidates.subList(0, episodeNumber); + } else { + delete = candidates; + } + + for (FeedItem item : delete) { + DBWriter.deleteFeedMediaOfItem(context, item.getId()); + } + + int counter = delete.size(); + + if (AppConfig.DEBUG) + Log.d(TAG, String.format( + "Auto-delete deleted %d episodes (%d requested)", counter, + episodeNumber)); + + return counter; + } + + public static void enqueueAllNewItems(final Context context) { + long[] unreadItems = DBReader.getUnreadItemIds(context); + DBWriter.addQueueItem(context, unreadItems); + } + + public static FeedItem getQueueSuccessorOfItem(Context context, + final long itemId, List queue) { + FeedItem result = null; if (queue == null) { - queue = DBReader.getQueue(context); + queue = DBReader.getQueue(context); + } + if (queue != null) { + Iterator iterator = queue.iterator(); + while (iterator.hasNext()) { + FeedItem item = iterator.next(); + if (item.getId() == itemId) { + if (iterator.hasNext()) { + result = iterator.next(); + } + break; + } + } } - if (queue != null) { - Iterator iterator = queue.iterator(); - while (iterator.hasNext()) { - FeedItem item = iterator.next(); - if (item.getId() == itemId) { - if (iterator.hasNext()) { - result = iterator.next(); - } - break; - } - } - } - return result; - } + return result; + } public static boolean isInQueue(Context context, final long feedItemId) { List queue = DBReader.getQueueIDList(context); return QueueAccess.IDListAccess(queue).contains(feedItemId); } - private static Feed searchFeedByIdentifyingValue(Context context, - String identifier) { - List feeds = DBReader.getFeedList(context); - for (Feed feed : feeds) { - if (feed.getIdentifyingValue().equals(identifier)) { - return feed; - } - } - return null; - } - - /** Get a FeedItem by its identifying value. */ - private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, - String identifier) { - for (FeedItem item : feed.getItems()) { - if (item.getIdentifyingValue().equals(identifier)) { - return item; - } - } - return null; - } - - public static synchronized Feed updateFeed(final Context context, - final Feed newFeed) { - // Look up feed in the feedslist - final Feed savedFeed = searchFeedByIdentifyingValue(context, - newFeed.getIdentifyingValue()); - if (savedFeed == null) { - if (AppConfig.DEBUG) - Log.d(TAG, - "Found no existing Feed with title " - + newFeed.getTitle() + ". Adding as new one."); - // Add a new Feed + private static Feed searchFeedByIdentifyingValue(Context context, + String identifier) { + List feeds = DBReader.getFeedList(context); + for (Feed feed : feeds) { + if (feed.getIdentifyingValue().equals(identifier)) { + return feed; + } + } + return null; + } + + /** + * Get a FeedItem by its identifying value. + */ + private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, + String identifier) { + for (FeedItem item : feed.getItems()) { + if (item.getIdentifyingValue().equals(identifier)) { + return item; + } + } + return null; + } + + public static synchronized Feed updateFeed(final Context context, + final Feed newFeed) { + // Look up feed in the feedslist + final Feed savedFeed = searchFeedByIdentifyingValue(context, + newFeed.getIdentifyingValue()); + if (savedFeed == null) { + if (AppConfig.DEBUG) + Log.d(TAG, + "Found no existing Feed with title " + + newFeed.getTitle() + ". Adding as new one."); + // Add a new Feed try { - DBWriter.addNewFeed(context, newFeed).get(); + DBWriter.addNewFeed(context, newFeed).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } - return newFeed; - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Feed with title " + newFeed.getTitle() - + " already exists. Syncing new with existing one."); - - savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); - if (savedFeed.compareWithOther(newFeed)) { - if (AppConfig.DEBUG) - Log.d(TAG, - "Feed has updated attribute values. Updating old feed's attributes"); - savedFeed.updateFromOther(newFeed); - } - // Look for new or updated Items - for (int idx = 0; idx < newFeed.getItems().size(); idx++) { - final FeedItem item = newFeed.getItems().get(idx); - FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, - item.getIdentifyingValue()); - if (oldItem == null) { - // item is new - final int i = idx; - item.setFeed(savedFeed); - savedFeed.getItems().add(i, item); - DBWriter.markItemRead(context, item.getId(), false); - } else { - oldItem.updateFromOther(item); - } - } - // update attributes - savedFeed.setLastUpdate(newFeed.getLastUpdate()); - savedFeed.setType(newFeed.getType()); + return newFeed; + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Feed with title " + newFeed.getTitle() + + " already exists. Syncing new with existing one."); + + savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); + if (savedFeed.compareWithOther(newFeed)) { + if (AppConfig.DEBUG) + Log.d(TAG, + "Feed has updated attribute values. Updating old feed's attributes"); + savedFeed.updateFromOther(newFeed); + } + // Look for new or updated Items + for (int idx = 0; idx < newFeed.getItems().size(); idx++) { + final FeedItem item = newFeed.getItems().get(idx); + FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, + item.getIdentifyingValue()); + if (oldItem == null) { + // item is new + final int i = idx; + item.setFeed(savedFeed); + savedFeed.getItems().add(i, item); + DBWriter.markItemRead(context, item.getId(), false); + } else { + oldItem.updateFromOther(item); + } + } + // update attributes + savedFeed.setLastUpdate(newFeed.getLastUpdate()); + savedFeed.setType(newFeed.getType()); try { - DBWriter.setCompleteFeed(context, savedFeed).get(); + DBWriter.setCompleteFeed(context, savedFeed).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } - new Thread() { - @Override - public void run() { - autodownloadUndownloadedItems(context); - } - }.start(); - return savedFeed; - } - } - - /** - * Searches the descriptions of FeedItems of a specific feed for a given - * string. - * - * @param feed - * The feed whose items should be searched. - * @param query - * The search string - * @param callback - * A callback which will be used to return the search result - * */ - public void searchFeedItemDescription(final Context context, - final Feed feed, final String query, QueryTaskCallback callback) { - new Thread((new QueryTask(context, new Handler(), callback) { - - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemDescriptions(feed, - query); - setResult(searchResult); - } - })).start(); - } - - /** - * Searches the 'contentEncoded' field of FeedItems of a specific feed for a - * given string. - * - * @param feed - * The feed whose items should be searched. - * @param query - * The search string - * @param callback - * A callback which will be used to return the search result - * */ - public void searchFeedItemContentEncoded(final Context context, - final Feed feed, final String query, QueryTaskCallback callback) { - new Thread((new QueryTask(context, new Handler(), callback) { - - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemContentEncoded(feed, - query); - setResult(searchResult); - } - })).start(); - } - - /** Is called by a FeedManagerTask after completion. */ - public interface TaskCallback { - void onCompletion(V result); - } - - /** Is called by a FeedManager.QueryTask after completion. */ - public interface QueryTaskCallback { - void handleResult(Cursor result); - - void onCompletion(); - } - - /** A runnable that can post a callback to a handler after completion. */ - abstract class Task implements Runnable { - private Handler handler; - private TaskCallback callback; - private V result; - - /** - * Standard contructor. No callbacks are going to be posted to a - * handler. - */ - public Task() { - super(); - } - - /** - * The Task will post a Runnable to 'handler' that will execute the - * 'callback' after completion. - */ - public Task(Handler handler, TaskCallback callback) { - super(); - this.handler = handler; - this.callback = callback; - } - - @Override - public final void run() { - execute(); - if (handler != null && callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onCompletion(result); - } - }); - } - } - - /** This method will be executed in the same thread as the run() method. */ - public abstract void execute(); - - public void setResult(V result) { - this.result = result; - } - } - - /** - * A runnable which should be used for database queries. The onCompletion - * method is executed on the database executor to handle Cursors correctly. - * This class automatically creates a PodDBAdapter object and closes it when - * it is no longer in use. - */ - abstract class QueryTask implements Runnable { - private QueryTaskCallback callback; - private Cursor result; - private Context context; - private Handler handler; - - public QueryTask(Context context, Handler handler, - QueryTaskCallback callback) { - this.callback = callback; - this.context = context; - this.handler = handler; - } - - @Override - public final void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - execute(adapter); - callback.handleResult(result); - if (result != null && !result.isClosed()) { - result.close(); - } - adapter.close(); - if (handler != null && callback != null) { - handler.post(new Runnable() { - - @Override - public void run() { - callback.onCompletion(); - } - - }); - } - } - - public abstract void execute(PodDBAdapter adapter); - - protected void setResult(Cursor c) { - result = c; - } - } + new Thread() { + @Override + public void run() { + autodownloadUndownloadedItems(context); + } + }.start(); + return savedFeed; + } + } + + /** + * Searches the titles of FeedItems of a specific feed for a given + * string. + * + * @param feedID The id of the feed whose items should be searched. + * @param query The search string + */ + public static FutureTask> searchFeedItemTitle(final Context context, + final long feedID, final String query) { + return new FutureTask>(new QueryTask>(context) { + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemTitles(feedID, + query); + List items = DBReader.extractItemlistFromCursor(context, searchResult); + DBReader.loadFeedDataOfFeedItemlist(context, items); + setResult(items); + searchResult.close(); + } + }); + } + + /** + * Searches the descriptions of FeedItems of a specific feed for a given + * string. + * + * @param feedID The id of the feed whose items should be searched. + * @param query The search string + */ + public static FutureTask> searchFeedItemDescription(final Context context, + final long feedID, final String query) { + return new FutureTask>(new QueryTask>(context) { + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemDescriptions(feedID, + query); + List items = DBReader.extractItemlistFromCursor(context, searchResult); + DBReader.loadFeedDataOfFeedItemlist(context, items); + setResult(items); + searchResult.close(); + } + }); + } + + /** + * Searches the 'contentEncoded' field of FeedItems of a specific feed for a + * given string. + * + * @param feedID The id of the feed whose items should be searched. + * @param query The search string + */ + public static FutureTask> searchFeedItemContentEncoded(final Context context, + final long feedID, final String query) { + return new FutureTask>(new QueryTask>(context) { + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemContentEncoded(feedID, + query); + List items = DBReader.extractItemlistFromCursor(context, searchResult); + DBReader.loadFeedDataOfFeedItemlist(context, items); + setResult(items); + searchResult.close(); + } + }); + } + + /** + * Searches chapters for a given string. + * + * @param feedID The id of the feed whose items should be searched. + * @param query The search string + */ + public static FutureTask> searchFeedItemChapters(final Context context, + final long feedID, final String query) { + return new FutureTask>(new QueryTask>(context) { + @Override + public void execute(PodDBAdapter adapter) { + Cursor searchResult = adapter.searchItemChapters(feedID, + query); + List items = DBReader.extractItemlistFromCursor(context, searchResult); + DBReader.loadFeedDataOfFeedItemlist(context, items); + setResult(items); + searchResult.close(); + } + }); + } + + /** + * A runnable which should be used for database queries. The onCompletion + * method is executed on the database executor to handle Cursors correctly. + * This class automatically creates a PodDBAdapter object and closes it when + * it is no longer in use. + */ + static abstract class QueryTask implements Callable { + private T result; + private Context context; + + public QueryTask(Context context) { + this.context = context; + } + + @Override + public T call() throws Exception { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + execute(adapter); + adapter.close(); + return result; + } + + public abstract void execute(PodDBAdapter adapter); + + protected void setResult(T result) { + this.result = result; + } + } } diff --git a/src/de/danoeh/antennapod/storage/FeedSearcher.java b/src/de/danoeh/antennapod/storage/FeedSearcher.java new file mode 100644 index 000000000..a16430056 --- /dev/null +++ b/src/de/danoeh/antennapod/storage/FeedSearcher.java @@ -0,0 +1,281 @@ +package de.danoeh.antennapod.storage; + +import android.content.Context; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.SearchResult; +import de.danoeh.antennapod.util.comparator.SearchResultValueComparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Performs search on Feeds and FeedItems + */ +public class FeedSearcher { + private static final String TAG = "FeedSearcher"; + + + /** + * Performs a search in all feeds or one specific feed. + */ + public static List performSearch(final Context context, + final String query, final long selectedFeed) { + final int values[] = {0, 0, 1, 2}; + final String[] subtitles = {context.getString(R.string.found_in_shownotes_label), + context.getString(R.string.found_in_shownotes_label), + context.getString(R.string.found_in_chapters_label), + context.getString(R.string.found_in_title_label)}; + + List result = new ArrayList(); + + FutureTask>[] tasks = new FutureTask[4]; + (tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run(); + (tasks[1] = DBTasks.searchFeedItemDescription(context, selectedFeed, query)).run(); + (tasks[2] = DBTasks.searchFeedItemChapters(context, selectedFeed, query)).run(); + (tasks[3] = DBTasks.searchFeedItemTitle(context, selectedFeed, query)).run(); + try { + for (int i = 0; i < tasks.length; i++) { + FutureTask task = tasks[i]; + List items = (List) task.get(); + for (FeedItem item : items) { + result.add(new SearchResult(item, values[i], subtitles[i])); + } + + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + Collections.sort(result, new SearchResultValueComparator()); + return result; + } + /* + *//** Performs a search in all feeds or one specific feed. *//* + public static ArrayList performSearch(final Context context, + final String query, final Feed selectedFeed) { + final String lcQuery = query.toLowerCase(); + final ArrayList result = new ArrayList(); + if (selectedFeed == null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Performing global search"); + if (AppConfig.DEBUG) + Log.d(TAG, "Searching Feed titles"); + searchFeedtitles(lcQuery, result); + } else if (AppConfig.DEBUG) { + Log.d(TAG, "Performing search on specific feed"); + } + + if (AppConfig.DEBUG) + Log.d(TAG, "Searching Feeditem titles"); + searchFeedItemTitles(lcQuery, result, selectedFeed); + + if (AppConfig.DEBUG) + Log.d(TAG, "Searching item-chaptertitles"); + searchFeedItemChapters(lcQuery, result, selectedFeed); + + Looper.prepare(); + DBTasks.searchFeedItemDescription(context, selectedFeed, lcQuery, + new DBTasks.QueryTaskCallback() { + + @Override + public void handleResult(Cursor cResult) { + searchFeedItemContentEncodedCursor(context, lcQuery, result, + selectedFeed, cResult); + + } + + @Override + public void onCompletion() { + DBTasks.searchFeedItemContentEncoded(context, + selectedFeed, lcQuery, + new DBTasks.QueryTaskCallback() { + + @Override + public void handleResult(Cursor cResult) { + searchFeedItemDescriptionCursor(context, + lcQuery, result, selectedFeed, + cResult); + } + + @Override + public void onCompletion() { + Looper.myLooper().quit(); + } + }); + } + }); + + Looper.loop(); + if (AppConfig.DEBUG) + Log.d(TAG, "Sorting results"); + Collections.sort(result, new SearchResultValueComparator()); + + return result; + } + + private static void searchFeedtitles(String query, + ArrayList destination) { + FeedManager manager = FeedManager.getInstance(); + for (Feed feed : manager.getFeeds()) { + SearchResult result = createSearchResult(feed, query, feed + .getTitle().toLowerCase(), VALUE_FEED_TITLE); + if (result != null) { + destination.add(result); + } + } + } + + private static void searchFeedItemTitles(String query, + ArrayList destination, Feed selectedFeed) { + FeedManager manager = FeedManager.getInstance(); + if (selectedFeed == null) { + for (Feed feed : manager.getFeeds()) { + searchFeedItemTitlesSingleFeed(query, destination, feed); + } + } else { + searchFeedItemTitlesSingleFeed(query, destination, selectedFeed); + } + } + + private static void searchFeedItemTitlesSingleFeed(String query, + ArrayList destination, Feed feed) { + for (FeedItem item : feed.getItems()) { + SearchResult result = createSearchResult(item, query, item + .getTitle().toLowerCase(), VALUE_ITEM_TITLE); + if (result != null) { + result.setSubtitle(PodcastApp.getInstance().getString( + R.string.found_in_title_label)); + destination.add(result); + } + + } + } + + private static void searchFeedItemChapters(String query, + ArrayList destination, Feed selectedFeed) { + if (selectedFeed == null) { + for (Feed feed : manager.getFeeds()) { + searchFeedItemChaptersSingleFeed(query, destination, feed); + } + } else { + searchFeedItemChaptersSingleFeed(query, destination, selectedFeed); + } + } + + private static void searchFeedItemChaptersSingleFeed(String query, + ArrayList destination, Feed feed) { + for (FeedItem item : feed.getItems()) { + if (item.getChapters() != null) { + for (Chapter sc : item.getChapters()) { + SearchResult result = createSearchResult(item, query, sc + .getTitle().toLowerCase(), VALUE_ITEM_CHAPTER); + if (result != null) { + result.setSubtitle(PodcastApp.getInstance().getString( + R.string.found_in_chapters_label)); + destination.add(result); + } + } + } + } + } + + private static void searchFeedItemDescriptionCursor(Context context, String query, + ArrayList destination, Feed feed, Cursor cursor) { + FeedManager manager = FeedManager.getInstance(); + List items = DBReader.extractItemlistFromCursor(cursor); + if (cursor.moveToFirst()) { + do { + final long itemId = cursor + .getLong(PodDBAdapter.IDX_FI_EXTRA_ID); + String content = cursor + .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION); + if (content != null) { + content = content.toLowerCase(); + final long feedId = cursor + .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED); + FeedItem item = null; + if (feed == null) { + item = manager.getFeedItem(itemId, feedId); + } else { + item = manager.getFeedItem(itemId, feed); + } + if (item != null) { + SearchResult searchResult = createSearchResult(item, + query, content, VALUE_ITEM_DESCRIPTION); + if (searchResult != null) { + searchResult.setSubtitle(PodcastApp.getInstance() + .getString( + R.string.found_in_shownotes_label)); + destination.add(searchResult); + + } + } + } + + } while (cursor.moveToNext()); + } + } + + private static void searchFeedItemContentEncodedCursor(Context context, String query, + ArrayList destination, Feed feed, Cursor cursor) { + if (cursor.moveToFirst()) { + do { + final long itemId = cursor + .getLong(PodDBAdapter.IDX_FI_EXTRA_ID); + String content = cursor + .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED); + if (content != null) { + content = content.toLowerCase(); + + final long feedId = cursor + .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED); + FeedItem item = null; + if (feed == null) { + item = manager.getFeedItem(itemId, feedId); + } else { + item = manager.getFeedItem(itemId, feed); + } + if (item != null) { + SearchResult searchResult = createSearchResult(item, + query, content, VALUE_ITEM_DESCRIPTION); + if (searchResult != null) { + searchResult.setSubtitle(PodcastApp.getInstance() + .getString( + R.string.found_in_shownotes_label)); + destination.add(searchResult); + } + } + } + } while (cursor.moveToNext()); + } + } + + private static SearchResult createSearchResult(FeedComponent component, + String query, String text, int baseValue) { + int bonus = 0; + boolean found = false; + // try word search + Pattern word = Pattern.compile("\b" + query + "\b"); + Matcher matcher = word.matcher(text); + found = matcher.find(); + if (found) { + bonus = VALUE_WORD_MATCH; + } else { + // search for other occurence + found = text.contains(query); + } + + if (found) { + return new SearchResult(component, baseValue + bonus); + } else { + return null; + } + }*/ + +} diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 4ef76fcd6..198d95d64 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -35,6 +35,9 @@ public class PodDBAdapter { /** Maximum number of arguments for IN-operator. */ public static final int IN_OPERATOR_MAXIMUM = 800; + /** Maximum number of entries per search request. */ + public static final int SEARCH_LIMIT = 30; + // ----------- Column indices // ----------- General indices public static final int KEY_ID_INDEX = 0; @@ -857,6 +860,18 @@ public class PodDBAdapter { } + public final int getNumberOfUnreadItems() { + final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS + + " WHERE " + KEY_READ + " = 0"; + Cursor c = db.rawQuery(query, null); + int result = 0; + if (c.moveToFirst()) { + result = c.getInt(0); + } + c.close(); + return result; + } + public final int getNumberOfDownloadedEpisodes() { final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_DOWNLOADED + " > 0"; @@ -888,17 +903,17 @@ public class PodDBAdapter { * * @return A cursor with all search results in SEL_FI_EXTRA selection. * */ - public Cursor searchItemDescriptions(Feed feed, String query) { - if (feed != null) { + public Cursor searchItemDescriptions(long feedID, String query) { + if (feedID != 0) { // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + "=? AND " + KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query) + "%'", - new String[] { String.valueOf(feed.getId()) }, null, null, + new String[] { String.valueOf(feedID) }, null, null, null); } else { // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query) + "%'", null, null, null, null); } @@ -910,23 +925,57 @@ public class PodDBAdapter { * * @return A cursor with all search results in SEL_FI_EXTRA selection. * */ - public Cursor searchItemContentEncoded(Feed feed, String query) { - if (feed != null) { + public Cursor searchItemContentEncoded(long feedID, String query) { + if (feedID != 0) { // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%" + prepareSearchQuery(query) + "%'", - new String[] { String.valueOf(feed.getId()) }, null, null, + new String[] { String.valueOf(feedID) }, null, null, null); } else { // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_CONTENT_ENCODED + " LIKE '%" + prepareSearchQuery(query) + "%'", null, null, null, null); } } + public Cursor searchItemTitles(long feedID, String query) { + if (feedID != 0) { + // search items in specific feed + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + + "=? AND " + KEY_TITLE + " LIKE '%" + + prepareSearchQuery(query) + "%'", + new String[] { String.valueOf(feedID) }, null, null, + null); + } else { + // search through all items + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, + KEY_TITLE + " LIKE '%" + + prepareSearchQuery(query) + "%'", null, null, + null, null); + } + } + + public Cursor searchItemChapters(long feedID, String searchQuery) { + final String query; + if (feedID != 0) { + query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + + TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + + feedID + " AND "+ TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" + + prepareSearchQuery(searchQuery) + "%'"; + } else { + query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + + TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" + + prepareSearchQuery(searchQuery) + "%'"; + } + return db.rawQuery(query, null); + } + /** Helper class for opening the Antennapod database. */ private static class PodDBHelper extends SQLiteOpenHelper { /** -- cgit v1.2.3 From 6d1a8535f5dbf9bcdd552ad382f2944f7b0d31ad Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Mon, 5 Aug 2013 01:27:38 +0200 Subject: Added FeedItemStatistics. Makes it possible to get number of (new, in progress) episodes of a feed without loading the whole list of items --- src/de/danoeh/antennapod/storage/DBReader.java | 893 +++++----- .../antennapod/storage/FeedItemStatistics.java | 42 + src/de/danoeh/antennapod/storage/PodDBAdapter.java | 1884 ++++++++++---------- 3 files changed, 1456 insertions(+), 1363 deletions(-) create mode 100644 src/de/danoeh/antennapod/storage/FeedItemStatistics.java (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 5d6dd9756..ababcdf78 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -23,90 +23,90 @@ import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; public final class DBReader { - private static final String TAG = "DBReader"; - - private DBReader() { - } - - public static List getFeedList(final Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting Feedlist"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor feedlistCursor = adapter.getAllFeedsCursor(); - List feeds = new ArrayList(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - static List getExpiredFeedsList(final Context context, final long expirationTime) { - if (AppConfig.DEBUG) - Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); - List feeds = new ArrayList(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - public static void loadFeedDataOfFeedItemlist(Context context, - List items) { - List feeds = getFeedList(context); - for (FeedItem item : items) { - for (Feed feed : feeds) { - if (feed.getId() == item.getFeedId()) { - item.setFeed(feed); - break; - } - } + private static final String TAG = "DBReader"; + + private DBReader() { + } + + public static List getFeedList(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting Feedlist"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor feedlistCursor = adapter.getAllFeedsCursor(); + List feeds = new ArrayList(feedlistCursor.getCount()); + + if (feedlistCursor.moveToFirst()) { + do { + Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); + feeds.add(feed); + } while (feedlistCursor.moveToNext()); + } + feedlistCursor.close(); + return feeds; + } + + static List getExpiredFeedsList(final Context context, final long expirationTime) { + if (AppConfig.DEBUG) + Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); + List feeds = new ArrayList(feedlistCursor.getCount()); + + if (feedlistCursor.moveToFirst()) { + do { + Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); + feeds.add(feed); + } while (feedlistCursor.moveToNext()); + } + feedlistCursor.close(); + return feeds; + } + + public static void loadFeedDataOfFeedItemlist(Context context, + List items) { + List feeds = getFeedList(context); + 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()); } - } - } + } + } - public static List getFeedItemList(Context context, - final Feed feed) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); + public static List getFeedItemList(Context context, + final Feed feed) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); - Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); + Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); - Collections.sort(items, new FeedItemPubdateComparator()); + Collections.sort(items, new FeedItemPubdateComparator()); - adapter.close(); + adapter.close(); - for (FeedItem item : items) { - item.setFeed(feed); - } + for (FeedItem item : items) { + item.setFeed(feed); + } - return items; - } + return items; + } static List extractItemlistFromCursor(Context context, Cursor itemlistCursor) { PodDBAdapter adapter = new PodDBAdapter(context); @@ -116,178 +116,178 @@ public final class DBReader { return result; } - private static List extractItemlistFromCursor( - PodDBAdapter adapter, Cursor itemlistCursor) { - ArrayList itemIds = new ArrayList(); - List items = new ArrayList( - itemlistCursor.getCount()); - - if (itemlistCursor.moveToFirst()) { - do { - FeedItem item = new FeedItem(); - - item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID)); - item.setTitle(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_TITLE)); - item.setLink(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_LINK)); - item.setPubDate(new Date(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE))); - item.setPaymentLink(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK)); - item.setFeedId(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_FEED)); - itemIds.add(String.valueOf(item.getId())); - - item.setRead((itemlistCursor + private static List extractItemlistFromCursor( + PodDBAdapter adapter, Cursor itemlistCursor) { + ArrayList itemIds = new ArrayList(); + List items = new ArrayList( + itemlistCursor.getCount()); + + if (itemlistCursor.moveToFirst()) { + do { + FeedItem item = new FeedItem(); + + item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID)); + item.setTitle(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_TITLE)); + item.setLink(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_LINK)); + item.setPubDate(new Date(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE))); + item.setPaymentLink(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK)); + item.setFeedId(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_FEED)); + itemIds.add(String.valueOf(item.getId())); + + item.setRead((itemlistCursor .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0)); - item.setItemIdentifier(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); - - // extract chapters - boolean hasSimpleChapters = itemlistCursor - .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0; - if (hasSimpleChapters) { - Cursor chapterCursor = adapter - .getSimpleChaptersOfFeedItemCursor(item); - if (chapterCursor.moveToFirst()) { - item.setChapters(new ArrayList()); - do { - int chapterType = chapterCursor - .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); - Chapter chapter = null; - long start = chapterCursor - .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); - String title = chapterCursor - .getString(PodDBAdapter.KEY_TITLE_INDEX); - String link = chapterCursor - .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); - - 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; - } - chapter.setId(chapterCursor - .getLong(PodDBAdapter.KEY_ID_INDEX)); - item.getChapters().add(chapter); - } while (chapterCursor.moveToNext()); - } - chapterCursor.close(); - } - items.add(item); - } while (itemlistCursor.moveToNext()); - } - - extractMediafromItemlist(adapter, items, itemIds); - return items; - } - - private static void extractMediafromItemlist(PodDBAdapter adapter, - List items, ArrayList itemIds) { - - List itemsCopy = new ArrayList(items); - Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds + item.setItemIdentifier(itemlistCursor + .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); + + // extract chapters + boolean hasSimpleChapters = itemlistCursor + .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0; + if (hasSimpleChapters) { + Cursor chapterCursor = adapter + .getSimpleChaptersOfFeedItemCursor(item); + if (chapterCursor.moveToFirst()) { + item.setChapters(new ArrayList()); + do { + int chapterType = chapterCursor + .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); + Chapter chapter = null; + long start = chapterCursor + .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); + String title = chapterCursor + .getString(PodDBAdapter.KEY_TITLE_INDEX); + String link = chapterCursor + .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); + + 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; + } + chapter.setId(chapterCursor + .getLong(PodDBAdapter.KEY_ID_INDEX)); + item.getChapters().add(chapter); + } while (chapterCursor.moveToNext()); + } + chapterCursor.close(); + } + items.add(item); + } while (itemlistCursor.moveToNext()); + } + + extractMediafromItemlist(adapter, items, itemIds); + return items; + } + + private static void extractMediafromItemlist(PodDBAdapter adapter, + List items, ArrayList itemIds) { + + List itemsCopy = new ArrayList(items); + Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds .toArray(new String[itemIds.size()])); - if (cursor.moveToFirst()) { - do { + if (cursor.moveToFirst()) { + do { long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); - // find matching feed item - FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); - if (item != null) { - item.setMedia(extractFeedMediaFromCursorRow(cursor)); + // find matching feed item + FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); + if (item != null) { + item.setMedia(extractFeedMediaFromCursorRow(cursor)); item.getMedia().setItem(item); - } - } while (cursor.moveToNext()); - cursor.close(); - } - } + } + } while (cursor.moveToNext()); + cursor.close(); + } + } private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) { - long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); - Date playbackCompletionDate = null; - long playbackCompletionTime = cursor - .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); - if (playbackCompletionTime > 0) { - playbackCompletionDate = new Date( - playbackCompletionTime); - } + long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + Date playbackCompletionDate = null; + long playbackCompletionTime = cursor + .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); + if (playbackCompletionTime > 0) { + playbackCompletionDate = new Date( + playbackCompletionTime); + } + + return new FeedMedia( + mediaId, + null, + cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), + cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), + cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), + cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), + cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), + cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), + cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, + playbackCompletionDate); + } + + private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, + Cursor cursor) { + Date lastUpdate = new Date( + cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX)); + Feed feed = new Feed(lastUpdate); + + feed.setId(cursor.getLong(PodDBAdapter.KEY_ID_INDEX)); + feed.setTitle(cursor.getString(PodDBAdapter.KEY_TITLE_INDEX)); + feed.setLink(cursor.getString(PodDBAdapter.KEY_LINK_INDEX)); + feed.setDescription(cursor + .getString(PodDBAdapter.KEY_DESCRIPTION_INDEX)); + feed.setPaymentLink(cursor + .getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX)); + feed.setAuthor(cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX)); + feed.setLanguage(cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX)); + feed.setType(cursor.getString(PodDBAdapter.KEY_TYPE_INDEX)); + feed.setFeedIdentifier(cursor + .getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX)); + long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX); + if (imageIndex != 0) { + feed.setImage(getFeedImage(adapter, imageIndex)); + feed.getImage().setFeed(feed); + } + feed.setFile_url(cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX)); + feed.setDownload_url(cursor + .getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX)); + feed.setDownloaded(cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0); + + return feed; + } - return new FeedMedia( - mediaId, - null, - cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), - cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), - cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), - cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), - cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), - cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), - cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, - playbackCompletionDate); + private static FeedItem getMatchingItemForMedia(long itemId, + List items) { + for (FeedItem item : items) { + if (item.getId() == itemId) { + return item; + } + } + return null; } - private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, - Cursor cursor) { - Date lastUpdate = new Date( - cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX)); - Feed feed = new Feed(lastUpdate); - - feed.setId(cursor.getLong(PodDBAdapter.KEY_ID_INDEX)); - feed.setTitle(cursor.getString(PodDBAdapter.KEY_TITLE_INDEX)); - feed.setLink(cursor.getString(PodDBAdapter.KEY_LINK_INDEX)); - feed.setDescription(cursor - .getString(PodDBAdapter.KEY_DESCRIPTION_INDEX)); - feed.setPaymentLink(cursor - .getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX)); - feed.setAuthor(cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX)); - feed.setLanguage(cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX)); - feed.setType(cursor.getString(PodDBAdapter.KEY_TYPE_INDEX)); - feed.setFeedIdentifier(cursor - .getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX)); - long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX); - if (imageIndex != 0) { - feed.setImage(getFeedImage(adapter, imageIndex)); - feed.getImage().setFeed(feed); - } - feed.setFile_url(cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX)); - feed.setDownload_url(cursor - .getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX)); - feed.setDownloaded(cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0); - - return feed; - } - - private static FeedItem getMatchingItemForMedia(long itemId, - List items) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return item; - } - } - return null; - } - - static List getQueue(Context context, PodDBAdapter adapter) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - Cursor itemlistCursor = adapter.getQueueCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - - return items; - } + static List getQueue(Context context, PodDBAdapter adapter) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting queue"); + + Cursor itemlistCursor = adapter.getQueueCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + loadFeedDataOfFeedItemlist(context, items); + + return items; + } public static List getQueueIDList(Context context) { PodDBAdapter adapter = new PodDBAdapter(context); @@ -312,178 +312,198 @@ public final class DBReader { return queueIds; } - public static List getQueue(Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List items = getQueue(context, adapter); - adapter.close(); - return items; - } - - public static List getDownloadedItems(Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting downloaded items"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getDownloadedItemsCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - Collections.sort(items, new FeedItemPubdateComparator()); - - adapter.close(); - return items; - - } - - public static List getUnreadItemsList(Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting unread items list"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getUnreadItemsCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - loadFeedDataOfFeedItemlist(context, items); - - adapter.close(); - - return items; - } - - public static long[] getUnreadItemIds(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor cursor = adapter.getUnreadItemIdsCursor(); - long[] itemIds = new long[cursor.getCount()]; - int i = 0; - if (cursor.moveToFirst()) { - do { - itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); - i++; - } while (cursor.moveToNext()); - } - return itemIds; - } - - public static List getPlaybackHistory(final Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Loading playback history"); - final int PLAYBACK_HISTORY_SIZE = 50; - - PodDBAdapter adapter = new PodDBAdapter(context); - 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++) { - itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX)); - } - mediaCursor.close(); - Cursor itemCursor = adapter.getFeedItemCursor(itemIds); - List items = extractItemlistFromCursor(adapter, itemCursor); + public static List getQueue(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting queue"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List items = getQueue(context, adapter); + adapter.close(); + return items; + } + + public static List getDownloadedItems(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting downloaded items"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getDownloadedItemsCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); loadFeedDataOfFeedItemlist(context, items); - itemCursor.close(); - - adapter.close(); - return items; - } - - public static List getDownloadLog(Context context) { - if (AppConfig.DEBUG) - Log.d(TAG, "Extracting DownloadLog"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor logCursor = adapter.getDownloadLogCursor(); - List downloadLog = new ArrayList( - logCursor.getCount()); - - if (logCursor.moveToFirst()) { - do { - long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - - long feedfileId = logCursor - .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); - int feedfileType = logCursor - .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); - boolean successful = logCursor - .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; - int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX); - String reasonDetailed = logCursor - .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); - String title = logCursor - .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); - Date completionDate = new Date( - logCursor - .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); - downloadLog.add(new DownloadStatus(id, title, feedfileId, - feedfileType, successful, reason, completionDate, - reasonDetailed)); - - } while (logCursor.moveToNext()); - } - logCursor.close(); - Collections.sort(downloadLog, new DownloadStatusComparator()); - return downloadLog; - } - - public static Feed getFeed(final Context context, final long feedId) { - if (AppConfig.DEBUG) - Log.d(TAG, "Loading feed with id " + feedId); - Feed feed = null; - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor feedCursor = adapter.getFeedCursor(feedId); - if (feedCursor.moveToFirst()) { - feed = extractFeedFromCursorRow(adapter, feedCursor); - feed.setItems(getFeedItemList(context, feed)); - } else { + Collections.sort(items, new FeedItemPubdateComparator()); + + adapter.close(); + return items; + + } + + public static List getUnreadItemsList(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting unread items list"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getUnreadItemsCursor(); + List items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + + loadFeedDataOfFeedItemlist(context, items); + + adapter.close(); + + return items; + } + + public static long[] getUnreadItemIds(Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor cursor = adapter.getUnreadItemIdsCursor(); + long[] itemIds = new long[cursor.getCount()]; + int i = 0; + if (cursor.moveToFirst()) { + do { + itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + i++; + } while (cursor.moveToNext()); + } + return itemIds; + } + + public static List getPlaybackHistory(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading playback history"); + final int PLAYBACK_HISTORY_SIZE = 50; + + PodDBAdapter adapter = new PodDBAdapter(context); + 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++) { + itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX)); + } + mediaCursor.close(); + Cursor itemCursor = adapter.getFeedItemCursor(itemIds); + List items = extractItemlistFromCursor(adapter, itemCursor); + loadFeedDataOfFeedItemlist(context, items); + itemCursor.close(); + + adapter.close(); + return items; + } + + public static List getDownloadLog(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Extracting DownloadLog"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor logCursor = adapter.getDownloadLogCursor(); + List downloadLog = new ArrayList( + logCursor.getCount()); + + if (logCursor.moveToFirst()) { + do { + long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + + long feedfileId = logCursor + .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); + int feedfileType = logCursor + .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); + boolean successful = logCursor + .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; + int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX); + String reasonDetailed = logCursor + .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); + String title = logCursor + .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); + Date completionDate = new Date( + logCursor + .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); + downloadLog.add(new DownloadStatus(id, title, feedfileId, + feedfileType, successful, reason, completionDate, + reasonDetailed)); + + } while (logCursor.moveToNext()); + } + logCursor.close(); + Collections.sort(downloadLog, new DownloadStatusComparator()); + return downloadLog; + } + + public static List getFeedStatisticsList(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List result = new ArrayList(); + Cursor cursor = adapter.getFeedStatisticsCursor(); + if (cursor.moveToFirst()) { + do { + result.add(new FeedItemStatistics(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_FEED), + cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NUM_ITEMS), + cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NEW_ITEMS), + cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES), + new Date(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_LATEST_EPISODE)))); + } while (cursor.moveToNext()); + } + + cursor.close(); + adapter.close(); + return result; + } + + public static Feed getFeed(final Context context, final long feedId) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feed with id " + feedId); + Feed feed = null; + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor feedCursor = adapter.getFeedCursor(feedId); + if (feedCursor.moveToFirst()) { + feed = extractFeedFromCursorRow(adapter, feedCursor); + feed.setItems(getFeedItemList(context, feed)); + } else { Log.e(TAG, "getFeed could not find feed with id " + feedId); } - adapter.close(); - return feed; - } - - static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) { - if (AppConfig.DEBUG) - Log.d(TAG, "Loading feeditem with id " + itemId); - FeedItem item = null; - - Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId)); - if (itemCursor.moveToFirst()) { - List list = extractItemlistFromCursor(adapter, itemCursor); - if (list.size() > 0) { - item = list.get(0); + adapter.close(); + return feed; + } + + static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feeditem with id " + itemId); + FeedItem item = null; + + Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId)); + if (itemCursor.moveToFirst()) { + List list = extractItemlistFromCursor(adapter, itemCursor); + if (list.size() > 0) { + item = list.get(0); loadFeedDataOfFeedItemlist(context, list); - } - } - return item; + } + } + return item; - } + } - public static FeedItem getFeedItem(final Context context, final long itemId) { - if (AppConfig.DEBUG) - Log.d(TAG, "Loading feeditem with id " + itemId); + public static FeedItem getFeedItem(final Context context, final long itemId) { + if (AppConfig.DEBUG) + Log.d(TAG, "Loading feeditem with id " + itemId); - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - FeedItem item = getFeedItem(context, itemId, adapter); - adapter.close(); - return item; + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + FeedItem item = getFeedItem(context, itemId, adapter); + adapter.close(); + return item; - } + } public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) { PodDBAdapter adapter = new PodDBAdapter(context); @@ -499,14 +519,14 @@ public final class DBReader { } adapter.close(); } - - public static int getNumberOfDownloadedEpisodes(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final int result = adapter.getNumberOfDownloadedEpisodes(); - adapter.close(); - return result; - } + + public static int getNumberOfDownloadedEpisodes(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final int result = adapter.getNumberOfDownloadedEpisodes(); + adapter.close(); + return result; + } public static int getNumberOfUnreadItems(final Context context) { PodDBAdapter adapter = new PodDBAdapter(context); @@ -519,10 +539,9 @@ public final class DBReader { /** * Searches the DB for a FeedImage of the given id. * - * @param imageId - * The id of the object + * @param imageId The id of the object * @return The found object - * */ + */ public static FeedImage getFeedImage(final Context context, final long imageId) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -531,37 +550,35 @@ public final class DBReader { return result; } - /** - * Searches the DB for a FeedImage of the given id. - * - * @param id - * The id of the object - * @return The found object - * */ - static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { - Cursor cursor = adapter.getImageOfFeedCursor(id); - if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { - throw new SQLException("No FeedImage found at index: " + id); - } - FeedImage image = new FeedImage(id, cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_TITLE)), - cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_FILE_URL)), - cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)), - cursor.getInt(cursor - .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0); - cursor.close(); - return image; - } + /** + * Searches the DB for a FeedImage of the given id. + * + * @param id The id of the object + * @return The found object + */ + static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { + Cursor cursor = adapter.getImageOfFeedCursor(id); + if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { + throw new SQLException("No FeedImage found at index: " + id); + } + FeedImage image = new FeedImage(id, cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_TITLE)), + cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_FILE_URL)), + cursor.getString(cursor + .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)), + cursor.getInt(cursor + .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0); + cursor.close(); + return image; + } /** * Searches the DB for a FeedMedia of the given id. * - * @param mediaId - * The id of the object + * @param mediaId The id of the object * @return The found object - * */ + */ public static FeedMedia getFeedMedia(final Context context, final long mediaId) { PodDBAdapter adapter = new PodDBAdapter(context); diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java new file mode 100644 index 000000000..17e838761 --- /dev/null +++ b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java @@ -0,0 +1,42 @@ +package de.danoeh.antennapod.storage; + +import java.util.Date; + +/** + * Contains information about a feed's items. + */ +public class FeedItemStatistics { + private long feedID; + private int numberOfItems; + private int numberOfNewItems; + private int numberOfInProgressItems; + private Date lastUpdate; + + public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) { + this.feedID = feedID; + this.numberOfItems = numberOfItems; + this.numberOfNewItems = numberOfNewItems; + this.numberOfInProgressItems = numberOfInProgressItems; + this.lastUpdate = lastUpdate; + } + + public long getFeedID() { + return feedID; + } + + public int getNumberOfItems() { + return numberOfItems; + } + + public int getNumberOfNewItems() { + return numberOfNewItems; + } + + public int getNumberOfInProgressItems() { + return numberOfInProgressItems; + } + + public Date getLastUpdate() { + return lastUpdate; + } +} diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 198d95d64..5171b6932 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -26,214 +26,224 @@ import de.danoeh.antennapod.service.download.DownloadStatus; /** * Implements methods for accessing the database - * */ + */ public class PodDBAdapter { - private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 9; - private static final String DATABASE_NAME = "Antennapod.db"; - - /** Maximum number of arguments for IN-operator. */ - public static final int IN_OPERATOR_MAXIMUM = 800; - - /** Maximum number of entries per search request. */ + private static final String TAG = "PodDBAdapter"; + private static final int DATABASE_VERSION = 9; + private static final String DATABASE_NAME = "Antennapod.db"; + + /** + * Maximum number of arguments for IN-operator. + */ + public static final int IN_OPERATOR_MAXIMUM = 800; + + /** + * Maximum number of entries per search request. + */ public static final int SEARCH_LIMIT = 30; - // ----------- Column indices - // ----------- General indices - public static final int KEY_ID_INDEX = 0; - public static final int KEY_TITLE_INDEX = 1; - public static final int KEY_FILE_URL_INDEX = 2; - public static final int KEY_DOWNLOAD_URL_INDEX = 3; - public static final int KEY_DOWNLOADED_INDEX = 4; - public static final int KEY_LINK_INDEX = 5; - public static final int KEY_DESCRIPTION_INDEX = 6; - public static final int KEY_PAYMENT_LINK_INDEX = 7; - // ----------- Feed indices - public static final int KEY_LAST_UPDATE_INDEX = 8; - public static final int KEY_LANGUAGE_INDEX = 9; - public static final int KEY_AUTHOR_INDEX = 10; - public static final int KEY_IMAGE_INDEX = 11; - public static final int KEY_TYPE_INDEX = 12; - public static final int KEY_FEED_IDENTIFIER_INDEX = 13; - // ----------- FeedItem indices - public static final int KEY_CONTENT_ENCODED_INDEX = 2; - public static final int KEY_PUBDATE_INDEX = 3; - public static final int KEY_READ_INDEX = 4; - public static final int KEY_MEDIA_INDEX = 8; - public static final int KEY_FEED_INDEX = 9; - public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10; - public static final int KEY_ITEM_IDENTIFIER_INDEX = 11; - // ---------- FeedMedia indices - public static final int KEY_DURATION_INDEX = 1; - public static final int KEY_POSITION_INDEX = 5; - public static final int KEY_SIZE_INDEX = 6; - public static final int KEY_MIME_TYPE_INDEX = 7; - public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; + // ----------- Column indices + // ----------- General indices + public static final int KEY_ID_INDEX = 0; + public static final int KEY_TITLE_INDEX = 1; + public static final int KEY_FILE_URL_INDEX = 2; + public static final int KEY_DOWNLOAD_URL_INDEX = 3; + public static final int KEY_DOWNLOADED_INDEX = 4; + public static final int KEY_LINK_INDEX = 5; + public static final int KEY_DESCRIPTION_INDEX = 6; + public static final int KEY_PAYMENT_LINK_INDEX = 7; + // ----------- Feed indices + public static final int KEY_LAST_UPDATE_INDEX = 8; + public static final int KEY_LANGUAGE_INDEX = 9; + public static final int KEY_AUTHOR_INDEX = 10; + public static final int KEY_IMAGE_INDEX = 11; + public static final int KEY_TYPE_INDEX = 12; + public static final int KEY_FEED_IDENTIFIER_INDEX = 13; + // ----------- FeedItem indices + public static final int KEY_CONTENT_ENCODED_INDEX = 2; + public static final int KEY_PUBDATE_INDEX = 3; + public static final int KEY_READ_INDEX = 4; + public static final int KEY_MEDIA_INDEX = 8; + public static final int KEY_FEED_INDEX = 9; + public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10; + public static final int KEY_ITEM_IDENTIFIER_INDEX = 11; + // ---------- FeedMedia indices + public static final int KEY_DURATION_INDEX = 1; + public static final int KEY_POSITION_INDEX = 5; + public static final int KEY_SIZE_INDEX = 6; + public static final int KEY_MIME_TYPE_INDEX = 7; + public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; public static final int KEY_MEDIA_FEEDITEM_INDEX = 9; - // --------- Download log indices - public static final int KEY_FEEDFILE_INDEX = 1; - public static final int KEY_FEEDFILETYPE_INDEX = 2; - public static final int KEY_REASON_INDEX = 3; - public static final int KEY_SUCCESSFUL_INDEX = 4; - public static final int KEY_COMPLETION_DATE_INDEX = 5; - public static final int KEY_REASON_DETAILED_INDEX = 6; - public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7; - // --------- Queue indices - public static final int KEY_FEEDITEM_INDEX = 1; - public static final int KEY_QUEUE_FEED_INDEX = 2; - // --------- Chapters indices - public static final int KEY_CHAPTER_START_INDEX = 2; - public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3; - public static final int KEY_CHAPTER_LINK_INDEX = 4; - public static final int KEY_CHAPTER_TYPE_INDEX = 5; - - // Key-constants - public static final String KEY_ID = "id"; - public static final String KEY_TITLE = "title"; - public static final String KEY_NAME = "name"; - public static final String KEY_LINK = "link"; - public static final String KEY_DESCRIPTION = "description"; - public static final String KEY_FILE_URL = "file_url"; - public static final String KEY_DOWNLOAD_URL = "download_url"; - public static final String KEY_PUBDATE = "pubDate"; - public static final String KEY_READ = "read"; - public static final String KEY_DURATION = "duration"; - public static final String KEY_POSITION = "position"; - public static final String KEY_SIZE = "filesize"; - public static final String KEY_MIME_TYPE = "mime_type"; - public static final String KEY_IMAGE = "image"; - public static final String KEY_FEED = "feed"; - public static final String KEY_MEDIA = "media"; - public static final String KEY_DOWNLOADED = "downloaded"; - public static final String KEY_LASTUPDATE = "last_update"; - public static final String KEY_FEEDFILE = "feedfile"; - public static final String KEY_REASON = "reason"; - public static final String KEY_SUCCESSFUL = "successful"; - public static final String KEY_FEEDFILETYPE = "feedfile_type"; - public static final String KEY_COMPLETION_DATE = "completion_date"; - public static final String KEY_FEEDITEM = "feeditem"; - public static final String KEY_CONTENT_ENCODED = "content_encoded"; - public static final String KEY_PAYMENT_LINK = "payment_link"; - public static final String KEY_START = "start"; - public static final String KEY_LANGUAGE = "language"; - public static final String KEY_AUTHOR = "author"; - public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; - public static final String KEY_TYPE = "type"; - public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; - public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; - public static final String KEY_REASON_DETAILED = "reason_detailed"; - public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; - public static final String KEY_CHAPTER_TYPE = "type"; - public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; - - // Table names - public static final String TABLE_NAME_FEEDS = "Feeds"; - public static final String TABLE_NAME_FEED_ITEMS = "FeedItems"; - public static final String TABLE_NAME_FEED_IMAGES = "FeedImages"; - public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia"; - public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog"; - public static final String TABLE_NAME_QUEUE = "Queue"; - public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters"; - - // SQL Statements for creating new tables - private static final String TABLE_PRIMARY_KEY = KEY_ID - + " INTEGER PRIMARY KEY AUTOINCREMENT ,"; - - private static final String CREATE_TABLE_FEEDS = "CREATE TABLE " - + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," - + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT," - + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," - + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR - + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT)";; - - private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " - + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE - + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," - + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," - + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," - + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)"; - - private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " - + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," - + KEY_DOWNLOADED + " INTEGER)"; - - private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " - + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION - + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL - + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION - + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," - + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," + // --------- Download log indices + public static final int KEY_FEEDFILE_INDEX = 1; + public static final int KEY_FEEDFILETYPE_INDEX = 2; + public static final int KEY_REASON_INDEX = 3; + public static final int KEY_SUCCESSFUL_INDEX = 4; + public static final int KEY_COMPLETION_DATE_INDEX = 5; + public static final int KEY_REASON_DETAILED_INDEX = 6; + public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7; + // --------- Queue indices + public static final int KEY_FEEDITEM_INDEX = 1; + public static final int KEY_QUEUE_FEED_INDEX = 2; + // --------- Chapters indices + public static final int KEY_CHAPTER_START_INDEX = 2; + public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3; + public static final int KEY_CHAPTER_LINK_INDEX = 4; + public static final int KEY_CHAPTER_TYPE_INDEX = 5; + + // Key-constants + public static final String KEY_ID = "id"; + public static final String KEY_TITLE = "title"; + public static final String KEY_NAME = "name"; + public static final String KEY_LINK = "link"; + public static final String KEY_DESCRIPTION = "description"; + public static final String KEY_FILE_URL = "file_url"; + public static final String KEY_DOWNLOAD_URL = "download_url"; + public static final String KEY_PUBDATE = "pubDate"; + public static final String KEY_READ = "read"; + public static final String KEY_DURATION = "duration"; + public static final String KEY_POSITION = "position"; + public static final String KEY_SIZE = "filesize"; + public static final String KEY_MIME_TYPE = "mime_type"; + public static final String KEY_IMAGE = "image"; + public static final String KEY_FEED = "feed"; + public static final String KEY_MEDIA = "media"; + public static final String KEY_DOWNLOADED = "downloaded"; + public static final String KEY_LASTUPDATE = "last_update"; + public static final String KEY_FEEDFILE = "feedfile"; + public static final String KEY_REASON = "reason"; + public static final String KEY_SUCCESSFUL = "successful"; + public static final String KEY_FEEDFILETYPE = "feedfile_type"; + public static final String KEY_COMPLETION_DATE = "completion_date"; + public static final String KEY_FEEDITEM = "feeditem"; + public static final String KEY_CONTENT_ENCODED = "content_encoded"; + public static final String KEY_PAYMENT_LINK = "payment_link"; + public static final String KEY_START = "start"; + public static final String KEY_LANGUAGE = "language"; + public static final String KEY_AUTHOR = "author"; + public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; + public static final String KEY_TYPE = "type"; + public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; + public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; + public static final String KEY_REASON_DETAILED = "reason_detailed"; + public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; + public static final String KEY_CHAPTER_TYPE = "type"; + public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; + + // Table names + public static final String TABLE_NAME_FEEDS = "Feeds"; + public static final String TABLE_NAME_FEED_ITEMS = "FeedItems"; + public static final String TABLE_NAME_FEED_IMAGES = "FeedImages"; + public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia"; + public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog"; + public static final String TABLE_NAME_QUEUE = "Queue"; + public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters"; + + // SQL Statements for creating new tables + private static final String TABLE_PRIMARY_KEY = KEY_ID + + " INTEGER PRIMARY KEY AUTOINCREMENT ,"; + + private static final String CREATE_TABLE_FEEDS = "CREATE TABLE " + + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE + + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," + + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT," + + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR + + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT," + + KEY_FEED_IDENTIFIER + " TEXT)"; + ; + + private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " + + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE + + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE + + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," + + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," + + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)"; + + private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " + + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE + + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," + + KEY_DOWNLOADED + " INTEGER)"; + + private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " + + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION + + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION + + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," + + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," + KEY_FEEDITEM + " INTEGER)"; - private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " - + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE - + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON - + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE - + " INTEGER," + KEY_REASON_DETAILED + " TEXT," - + KEY_DOWNLOADSTATUS_TITLE + " TEXT)"; - - private static final String CREATE_TABLE_QUEUE = "CREATE TABLE " - + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY," - + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)"; - - private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE " - + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER," - + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)"; - - private SQLiteDatabase db; - private final Context context; - private PodDBHelper helper; - - /** - * Select all columns from the feeditems-table except description and - * content-encoded. - */ - private static final String[] SEL_FI_SMALL = { - TABLE_NAME_FEED_ITEMS + "." + KEY_ID, - TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE, - TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE, - TABLE_NAME_FEED_ITEMS + "." + KEY_READ, - TABLE_NAME_FEED_ITEMS + "." + KEY_LINK, - TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, - TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, - TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, - TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER }; - - /** Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries. */ - private static final String SEL_FI_SMALL_STR; - static { - String selFiSmall = Arrays.toString(SEL_FI_SMALL); - SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1); - } - - // column indices for SEL_FI_SMALL - - public static final int IDX_FI_SMALL_ID = 0; - public static final int IDX_FI_SMALL_TITLE = 1; - public static final int IDX_FI_SMALL_PUBDATE = 2; - public static final int IDX_FI_SMALL_READ = 3; - public static final int IDX_FI_SMALL_LINK = 4; - public static final int IDX_FI_SMALL_PAYMENT_LINK = 5; - public static final int IDX_FI_SMALL_MEDIA = 6; - public static final int IDX_FI_SMALL_FEED = 7; - public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8; - public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9; - - /** Select id, description and content-encoded column from feeditems. */ - public static final String[] SEL_FI_EXTRA = { KEY_ID, KEY_DESCRIPTION, - KEY_CONTENT_ENCODED, KEY_FEED }; - - // column indices for SEL_FI_EXTRA - - public static final int IDX_FI_EXTRA_ID = 0; - public static final int IDX_FI_EXTRA_DESCRIPTION = 1; - public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2; - public static final int IDX_FI_EXTRA_FEED = 3; + private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " + + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE + + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON + + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE + + " INTEGER," + KEY_REASON_DETAILED + " TEXT," + + KEY_DOWNLOADSTATUS_TITLE + " TEXT)"; + + private static final String CREATE_TABLE_QUEUE = "CREATE TABLE " + + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY," + + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)"; + + private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE " + + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE + + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER," + + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)"; + + private SQLiteDatabase db; + private final Context context; + private PodDBHelper helper; + + /** + * Select all columns from the feeditems-table except description and + * content-encoded. + */ + private static final String[] SEL_FI_SMALL = { + TABLE_NAME_FEED_ITEMS + "." + KEY_ID, + TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE, + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE, + TABLE_NAME_FEED_ITEMS + "." + KEY_READ, + TABLE_NAME_FEED_ITEMS + "." + KEY_LINK, + TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER}; + + /** + * Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries. + */ + private static final String SEL_FI_SMALL_STR; + + static { + String selFiSmall = Arrays.toString(SEL_FI_SMALL); + SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1); + } + + // column indices for SEL_FI_SMALL + + public static final int IDX_FI_SMALL_ID = 0; + public static final int IDX_FI_SMALL_TITLE = 1; + public static final int IDX_FI_SMALL_PUBDATE = 2; + public static final int IDX_FI_SMALL_READ = 3; + public static final int IDX_FI_SMALL_LINK = 4; + public static final int IDX_FI_SMALL_PAYMENT_LINK = 5; + public static final int IDX_FI_SMALL_MEDIA = 6; + public static final int IDX_FI_SMALL_FEED = 7; + public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8; + public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9; + + /** + * Select id, description and content-encoded column from feeditems. + */ + public static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION, + KEY_CONTENT_ENCODED, KEY_FEED}; + + // column indices for SEL_FI_EXTRA + + public static final int IDX_FI_EXTRA_ID = 0; + public static final int IDX_FI_EXTRA_DESCRIPTION = 1; + public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2; + public static final int IDX_FI_EXTRA_FEED = 3; static PodDBHelper dbHelperSingleton; @@ -244,492 +254,494 @@ public class PodDBAdapter { return dbHelperSingleton; } - public PodDBAdapter(Context c) { - this.context = c; - helper = getDbHelperSingleton(c.getApplicationContext()); - } - - public PodDBAdapter open() { - if (db == null || !db.isOpen() || db.isReadOnly()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Opening DB"); - try { - db = helper.getWritableDatabase(); - } catch (SQLException ex) { - ex.printStackTrace(); - db = helper.getReadableDatabase(); - } - } - return this; - } - - public void close() { - if (AppConfig.DEBUG) - Log.d(TAG, "Closing DB"); - //db.close(); - } - - /** - * Inserts or updates a feed entry - * - * @return the id of the entry - * */ - public long setFeed(Feed feed) { - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, feed.getTitle()); - values.put(KEY_LINK, feed.getLink()); - values.put(KEY_DESCRIPTION, feed.getDescription()); - values.put(KEY_PAYMENT_LINK, feed.getPaymentLink()); - values.put(KEY_AUTHOR, feed.getAuthor()); - values.put(KEY_LANGUAGE, feed.getLanguage()); - if (feed.getImage() != null) { - if (feed.getImage().getId() == 0) { - setImage(feed.getImage()); - } - values.put(KEY_IMAGE, feed.getImage().getId()); - } - - values.put(KEY_FILE_URL, feed.getFile_url()); - values.put(KEY_DOWNLOAD_URL, feed.getDownload_url()); - values.put(KEY_DOWNLOADED, feed.isDownloaded()); - values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime()); - values.put(KEY_TYPE, feed.getType()); - values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); - if (feed.getId() == 0) { - // Create new entry - if (AppConfig.DEBUG) - Log.d(this.toString(), "Inserting new Feed into db"); - feed.setId(db.insert(TABLE_NAME_FEEDS, null, values)); - } else { - if (AppConfig.DEBUG) - Log.d(this.toString(), "Updating existing Feed in db"); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", - new String[] { Long.toString(feed.getId()) }); - } - return feed.getId(); - } - - /** - * Inserts or updates an image entry - * - * @return the id of the entry - * */ - public long setImage(FeedImage image) { + public PodDBAdapter(Context c) { + this.context = c; + helper = getDbHelperSingleton(c.getApplicationContext()); + } + + public PodDBAdapter open() { + if (db == null || !db.isOpen() || db.isReadOnly()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Opening DB"); + try { + db = helper.getWritableDatabase(); + } catch (SQLException ex) { + ex.printStackTrace(); + db = helper.getReadableDatabase(); + } + } + return this; + } + + public void close() { + if (AppConfig.DEBUG) + Log.d(TAG, "Closing DB"); + //db.close(); + } + + /** + * Inserts or updates a feed entry + * + * @return the id of the entry + */ + public long setFeed(Feed feed) { + ContentValues values = new ContentValues(); + values.put(KEY_TITLE, feed.getTitle()); + values.put(KEY_LINK, feed.getLink()); + values.put(KEY_DESCRIPTION, feed.getDescription()); + values.put(KEY_PAYMENT_LINK, feed.getPaymentLink()); + values.put(KEY_AUTHOR, feed.getAuthor()); + values.put(KEY_LANGUAGE, feed.getLanguage()); + if (feed.getImage() != null) { + if (feed.getImage().getId() == 0) { + setImage(feed.getImage()); + } + values.put(KEY_IMAGE, feed.getImage().getId()); + } + + values.put(KEY_FILE_URL, feed.getFile_url()); + values.put(KEY_DOWNLOAD_URL, feed.getDownload_url()); + values.put(KEY_DOWNLOADED, feed.isDownloaded()); + values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime()); + values.put(KEY_TYPE, feed.getType()); + values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); + if (feed.getId() == 0) { + // Create new entry + if (AppConfig.DEBUG) + Log.d(this.toString(), "Inserting new Feed into db"); + feed.setId(db.insert(TABLE_NAME_FEEDS, null, values)); + } else { + if (AppConfig.DEBUG) + Log.d(this.toString(), "Updating existing Feed in db"); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", + new String[]{String.valueOf(feed.getId())}); + } + return feed.getId(); + } + + /** + * Inserts or updates an image entry + * + * @return the id of the entry + */ + public long setImage(FeedImage image) { db.beginTransaction(); - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, image.getTitle()); - values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); - values.put(KEY_DOWNLOADED, image.isDownloaded()); - values.put(KEY_FILE_URL, image.getFile_url()); - if (image.getId() == 0) { - image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); - } else { - db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", - new String[] { String.valueOf(image.getId()) }); - } - if (image.getFeed() != null && image.getFeed().getId() != 0 ) { + ContentValues values = new ContentValues(); + values.put(KEY_TITLE, image.getTitle()); + values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); + values.put(KEY_DOWNLOADED, image.isDownloaded()); + values.put(KEY_FILE_URL, image.getFile_url()); + if (image.getId() == 0) { + image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); + } else { + db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", + new String[]{String.valueOf(image.getId())}); + } + if (image.getFeed() != null && image.getFeed().getId() != 0) { values.clear(); values.put(KEY_IMAGE, image.getId()); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[] {String.valueOf(image.getFeed().getId())}); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getFeed().getId())}); } db.setTransactionSuccessful(); db.endTransaction(); - return image.getId(); - } - - /** - * Inserts or updates an image entry - * - * @return the id of the entry - */ - public long setMedia(FeedMedia media) { - ContentValues values = new ContentValues(); - values.put(KEY_DURATION, media.getDuration()); - values.put(KEY_POSITION, media.getPosition()); - values.put(KEY_SIZE, media.getSize()); - values.put(KEY_MIME_TYPE, media.getMime_type()); - values.put(KEY_DOWNLOAD_URL, media.getDownload_url()); - values.put(KEY_DOWNLOADED, media.isDownloaded()); - values.put(KEY_FILE_URL, media.getFile_url()); - if (media.getPlaybackCompletionDate() != null) { - values.put(KEY_PLAYBACK_COMPLETION_DATE, media - .getPlaybackCompletionDate().getTime()); - } else { - values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); - } + return image.getId(); + } + + /** + * Inserts or updates an image entry + * + * @return the id of the entry + */ + public long setMedia(FeedMedia media) { + ContentValues values = new ContentValues(); + values.put(KEY_DURATION, media.getDuration()); + values.put(KEY_POSITION, media.getPosition()); + values.put(KEY_SIZE, media.getSize()); + values.put(KEY_MIME_TYPE, media.getMime_type()); + values.put(KEY_DOWNLOAD_URL, media.getDownload_url()); + values.put(KEY_DOWNLOADED, media.isDownloaded()); + values.put(KEY_FILE_URL, media.getFile_url()); + if (media.getPlaybackCompletionDate() != null) { + values.put(KEY_PLAYBACK_COMPLETION_DATE, media + .getPlaybackCompletionDate().getTime()); + } else { + values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); + } if (media.getItem() != null) { values.put(KEY_FEEDITEM, media.getItem().getId()); } - if (media.getId() == 0) { - media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values)); - } else { - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[] { String.valueOf(media.getId()) }); - } - return media.getId(); - } + if (media.getId() == 0) { + media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values)); + } else { + db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", + new String[]{String.valueOf(media.getId())}); + } + return media.getId(); + } public void setFeedMediaPosition(FeedMedia media) { if (media.getId() != 0) { ContentValues values = new ContentValues(); values.put(KEY_POSITION, media.getPosition()); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[] { String.valueOf(media.getId()) }); + new String[]{String.valueOf(media.getId())}); } else { Log.e(TAG, "setFeedMediaPosition: ID of media was 0"); } } - /** - * Insert all FeedItems of a feed and the feed object itself in a single - * transaction - */ - public void setCompleteFeed(Feed feed) { - db.beginTransaction(); - setFeed(feed); - for (FeedItem item : feed.getItemsArray()) { - setFeedItem(item); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void setFeedItemlist(List items) { - db.beginTransaction(); - for (FeedItem item : items) { - setFeedItem(item); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public long setSingleFeedItem(FeedItem item) { - db.beginTransaction(); - long result = setFeedItem(item); - db.setTransactionSuccessful(); - db.endTransaction(); - return result; - } - - /** - * Inserts or updates a feeditem entry - * - * @return the id of the entry - */ - private long setFeedItem(FeedItem item) { - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, item.getTitle()); - values.put(KEY_LINK, item.getLink()); - if (item.getDescription() != null) { - values.put(KEY_DESCRIPTION, item.getDescription()); - } - if (item.getContentEncoded() != null) { - values.put(KEY_CONTENT_ENCODED, item.getContentEncoded()); - } - values.put(KEY_PUBDATE, item.getPubDate().getTime()); - values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); - if (item.getFeed().getId() == 0) { - setFeed(item.getFeed()); - } - values.put(KEY_FEED, item.getFeed().getId()); - values.put(KEY_READ, item.isRead()); - values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); - values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); - if (item.getId() == 0) { - item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); - } else { - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", - new String[] { String.valueOf(item.getId()) }); - } + /** + * Insert all FeedItems of a feed and the feed object itself in a single + * transaction + */ + public void setCompleteFeed(Feed feed) { + db.beginTransaction(); + setFeed(feed); + for (FeedItem item : feed.getItemsArray()) { + setFeedItem(item); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public void setFeedItemlist(List items) { + db.beginTransaction(); + for (FeedItem item : items) { + setFeedItem(item); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public long setSingleFeedItem(FeedItem item) { + db.beginTransaction(); + long result = setFeedItem(item); + db.setTransactionSuccessful(); + db.endTransaction(); + return result; + } + + /** + * Inserts or updates a feeditem entry + * + * @return the id of the entry + */ + private long setFeedItem(FeedItem item) { + ContentValues values = new ContentValues(); + values.put(KEY_TITLE, item.getTitle()); + values.put(KEY_LINK, item.getLink()); + if (item.getDescription() != null) { + values.put(KEY_DESCRIPTION, item.getDescription()); + } + if (item.getContentEncoded() != null) { + values.put(KEY_CONTENT_ENCODED, item.getContentEncoded()); + } + values.put(KEY_PUBDATE, item.getPubDate().getTime()); + values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); + if (item.getFeed().getId() == 0) { + setFeed(item.getFeed()); + } + values.put(KEY_FEED, item.getFeed().getId()); + values.put(KEY_READ, item.isRead()); + values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); + values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); + if (item.getId() == 0) { + item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); + } else { + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", + new String[]{String.valueOf(item.getId())}); + } if (item.getMedia() != null) { if (item.getMedia().getId() == 0) { setMedia(item.getMedia()); } } - if (item.getChapters() != null) { - setChapters(item); - } - return item.getId(); - } - - public void setFeedItemRead(boolean read, long itemId, long mediaId, - boolean resetMediaPosition) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - - values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, "?=?", new String[] { KEY_ID, - Long.toString(itemId) }); - - if (resetMediaPosition) { - values.clear(); - values.put(KEY_POSITION, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, "?=?", new String[] { - KEY_ID, Long.toString(mediaId) }); - } - - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void setFeedItemRead(boolean read, long... itemIds) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - for (long id : itemIds) { - values.clear(); - values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, "?=?", new String[] { - KEY_ID, Long.toString(id) }); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void setChapters(FeedItem item) { - ContentValues values = new ContentValues(); - for (Chapter chapter : item.getChapters()) { - values.put(KEY_TITLE, chapter.getTitle()); - values.put(KEY_START, chapter.getStart()); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_LINK, chapter.getLink()); - values.put(KEY_CHAPTER_TYPE, chapter.getChapterType()); - if (chapter.getId() == 0) { - chapter.setId(db - .insert(TABLE_NAME_SIMPLECHAPTERS, null, values)); - } else { - db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?", - new String[] { String.valueOf(chapter.getId()) }); - } - } - } - - /** - * Inserts or updates a download status. - * */ - public long setDownloadStatus(DownloadStatus status) { - ContentValues values = new ContentValues(); - values.put(KEY_FEEDFILE, status.getFeedfileId()); - values.put(KEY_FEEDFILETYPE, status.getFeedfileType()); - values.put(KEY_REASON, status.getReason()); - values.put(KEY_SUCCESSFUL, status.isSuccessful()); - values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime()); - values.put(KEY_REASON_DETAILED, status.getReasonDetailed()); - values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle()); - if (status.getId() == 0) { - status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values)); - } else { - db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?", - new String[] { String.valueOf(status.getId()) }); - } - - return status.getId(); - } - - public long getDownloadLogSize() { - Cursor result = db.rawQuery("SELECT COUNT(?) AS ? FROM ?", - new String[] { KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG }); - long count = result.getLong(KEY_ID_INDEX); - result.close(); - return count; - } - - public void removeDownloadLogItems(long count) { - if (count > 0) { - db.rawQuery("DELETE FROM ? ORDER BY ? ASC LIMIT ?", - new String[] { TABLE_NAME_DOWNLOAD_LOG, - KEY_COMPLETION_DATE, Long.toString(count) }); - } - } - - public void setQueue(List queue) { - ContentValues values = new ContentValues(); - db.beginTransaction(); - db.delete(TABLE_NAME_QUEUE, null, null); - for (int i = 0; i < queue.size(); i++) { - FeedItem item = queue.get(i); - values.put(KEY_ID, i); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_FEED, item.getFeed().getId()); - db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, - SQLiteDatabase.CONFLICT_REPLACE); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void clearQueue() { - db.delete(TABLE_NAME_QUEUE, null, null); - } - - public void removeFeedMedia(FeedMedia media) { - db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?", - new String[] { String.valueOf(media.getId()) }); - } - - public void removeChaptersOfItem(FeedItem item) { - db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?", - new String[] { String.valueOf(item.getId()) }); - } - - public void removeFeedImage(FeedImage image) { - db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?", - new String[] { String.valueOf(image.getId()) }); - } - - /** Remove a FeedItem and its FeedMedia entry. */ - public void removeFeedItem(FeedItem item) { - if (item.getMedia() != null) { - removeFeedMedia(item.getMedia()); - } - if (item.getChapters() != null) { - removeChaptersOfItem(item); - } - db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?", - new String[] { String.valueOf(item.getId()) }); - } - - /** Remove a feed with all its FeedItems and Media entries. */ - public void removeFeed(Feed feed) { - db.beginTransaction(); - if (feed.getImage() != null) { - removeFeedImage(feed.getImage()); - } - for (FeedItem item : feed.getItemsArray()) { - removeFeedItem(item); - } - db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", - new String[] { String.valueOf(feed.getId()) }); - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void removeDownloadStatus(DownloadStatus remove) { - db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?", - new String[] { String.valueOf(remove.getId()) }); - } - - public void clearPlaybackHistory() { - ContentValues values = new ContentValues(); - values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, null, null); - } - - /** - * Get all Feeds from the Feed Table. - * - * @return The cursor of the query - * */ - public final Cursor getAllFeedsCursor() { - open(); - Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null, - KEY_TITLE + " ASC"); - return c; - } - - public final Cursor getExpiredFeedsCursor(long expirationTime) { - open(); - Cursor c = db.query(TABLE_NAME_FEEDS, null, "? 0) { + db.rawQuery("DELETE FROM ? ORDER BY ? ASC LIMIT ?", + new String[]{TABLE_NAME_DOWNLOAD_LOG, + KEY_COMPLETION_DATE, String.valueOf(count)}); + } + } + + public void setQueue(List queue) { + ContentValues values = new ContentValues(); + db.beginTransaction(); + db.delete(TABLE_NAME_QUEUE, null, null); + for (int i = 0; i < queue.size(); i++) { + FeedItem item = queue.get(i); + values.put(KEY_ID, i); + values.put(KEY_FEEDITEM, item.getId()); + values.put(KEY_FEED, item.getFeed().getId()); + db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, + SQLiteDatabase.CONFLICT_REPLACE); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public void clearQueue() { + db.delete(TABLE_NAME_QUEUE, null, null); + } + + public void removeFeedMedia(FeedMedia media) { + db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?", + new String[]{String.valueOf(media.getId())}); + } + + public void removeChaptersOfItem(FeedItem item) { + db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?", + new String[]{String.valueOf(item.getId())}); + } + + public void removeFeedImage(FeedImage image) { + db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?", + new String[]{String.valueOf(image.getId())}); + } + + /** + * Remove a FeedItem and its FeedMedia entry. + */ + public void removeFeedItem(FeedItem item) { + if (item.getMedia() != null) { + removeFeedMedia(item.getMedia()); + } + if (item.getChapters() != null) { + removeChaptersOfItem(item); + } + db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?", + new String[]{String.valueOf(item.getId())}); + } + + /** + * Remove a feed with all its FeedItems and Media entries. + */ + public void removeFeed(Feed feed) { + db.beginTransaction(); + if (feed.getImage() != null) { + removeFeedImage(feed.getImage()); + } + for (FeedItem item : feed.getItemsArray()) { + removeFeedItem(item); + } + db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", + new String[]{String.valueOf(feed.getId())}); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public void removeDownloadStatus(DownloadStatus remove) { + db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?", + new String[]{String.valueOf(remove.getId())}); + } + + public void clearPlaybackHistory() { + ContentValues values = new ContentValues(); + values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); + db.update(TABLE_NAME_FEED_MEDIA, values, null, null); + } + + /** + * Get all Feeds from the Feed Table. + * + * @return The cursor of the query + */ + public final Cursor getAllFeedsCursor() { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null, + KEY_TITLE + " ASC"); + return c; + } + + public final Cursor getExpiredFeedsCursor(long expirationTime) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, "?0"; - Cursor c = db.rawQuery(query, null); - return c; - } - - /** - * Returns a cursor which contains feed media objects with a playback - * completion date in descending order. - * - * @param limit - * The maximum row count of the returned cursor. Must be an - * integer >= 0. - * @throws IllegalArgumentException - * if limit < 0 - */ - public final Cursor getCompletedMediaCursor(int limit) { - if (limit < 0) { - throw new IllegalArgumentException("Limit must be >= 0"); - } - open(); - Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, - KEY_PLAYBACK_COMPLETION_DATE + " IS NOT NULL", null, null, - null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit); - return c; - } + Cursor c = db.rawQuery(query, null); + return c; + } + + /** + * Returns a cursor which contains feed media objects with a playback + * completion date in descending order. + * + * @param limit The maximum row count of the returned cursor. Must be an + * integer >= 0. + * @throws IllegalArgumentException if limit < 0 + */ + public final Cursor getCompletedMediaCursor(int limit) { + if (limit < 0) { + throw new IllegalArgumentException("Limit must be >= 0"); + } + open(); + Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, + KEY_PLAYBACK_COMPLETION_DATE + " IS NOT NULL", null, null, + null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit); + return c; + } public final Cursor getSingleFeedMediaCursor(long id) { - return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[] {Long.toString(id)}, null, null, null); + return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null); } - public final Cursor getFeedMediaCursorByItemID(String... mediaIds) { - int length = mediaIds.length; - if (length > IN_OPERATOR_MAXIMUM) { - Log.w(TAG, "Length of id array is larger than " - + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors"); - int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1; - Cursor[] cursors = new Cursor[numCursors]; - for (int i = 0; i < numCursors; i++) { - int neededLength = 0; - String[] parts = null; - final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM; - - if (elementsLeft >= IN_OPERATOR_MAXIMUM) { - neededLength = IN_OPERATOR_MAXIMUM; - parts = Arrays.copyOfRange(mediaIds, i - * IN_OPERATOR_MAXIMUM, (i + 1) - * IN_OPERATOR_MAXIMUM); - } else { - neededLength = elementsLeft; - parts = Arrays.copyOfRange(mediaIds, i - * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM) - + neededLength); - } - - cursors[i] = db.rawQuery("SELECT * FROM " - + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN " - + buildInOperator(neededLength), parts); - } - return new MergeCursor(cursors); - } else { - return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN " - + buildInOperator(length), mediaIds, null, null, null); - } - } - - /** Builds an IN-operator argument depending on the number of items. */ - private String buildInOperator(int size) { + public final Cursor getFeedMediaCursorByItemID(String... mediaIds) { + int length = mediaIds.length; + if (length > IN_OPERATOR_MAXIMUM) { + Log.w(TAG, "Length of id array is larger than " + + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors"); + int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1; + Cursor[] cursors = new Cursor[numCursors]; + for (int i = 0; i < numCursors; i++) { + int neededLength = 0; + String[] parts = null; + final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM; + + if (elementsLeft >= IN_OPERATOR_MAXIMUM) { + neededLength = IN_OPERATOR_MAXIMUM; + parts = Arrays.copyOfRange(mediaIds, i + * IN_OPERATOR_MAXIMUM, (i + 1) + * IN_OPERATOR_MAXIMUM); + } else { + neededLength = elementsLeft; + parts = Arrays.copyOfRange(mediaIds, i + * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM) + + neededLength); + } + + cursors[i] = db.rawQuery("SELECT * FROM " + + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN " + + buildInOperator(neededLength), parts); + } + return new MergeCursor(cursors); + } else { + return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN " + + buildInOperator(length), mediaIds, null, null, null); + } + } + + /** + * Builds an IN-operator argument depending on the number of items. + */ + private String buildInOperator(int size) { if (size == 1) { return "(?)"; } - StringBuffer buffer = new StringBuffer("("); - for (int i = 0; i < size - 1; i++) { - buffer.append("?,"); - } - buffer.append("?)"); - return buffer.toString(); - } - - public final Cursor getFeedCursor(final long id) { - open(); - Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null, - null, null, null); - return c; - } - - public final Cursor getFeedItemCursor(final String... ids) { - if (ids.length > IN_OPERATOR_MAXIMUM) { - throw new IllegalArgumentException( - "number of IDs must not be larger than " - + IN_OPERATOR_MAXIMUM); - } - - open(); - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_ID + " IN " - + buildInOperator(ids.length), ids, null, null, null); - - } + StringBuffer buffer = new StringBuffer("("); + for (int i = 0; i < size - 1; i++) { + buffer.append("?,"); + } + buffer.append("?)"); + return buffer.toString(); + } + + public final Cursor getFeedCursor(final long id) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null, + null, null, null); + return c; + } + + public final Cursor getFeedItemCursor(final String... ids) { + if (ids.length > IN_OPERATOR_MAXIMUM) { + throw new IllegalArgumentException( + "number of IDs must not be larger than " + + IN_OPERATOR_MAXIMUM); + } + + open(); + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_ID + " IN " + + buildInOperator(ids.length), ids, null, null, null); + + } public final int getNumberOfUnreadItems() { final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS + @@ -872,75 +884,75 @@ public class PodDBAdapter { return result; } - public final int getNumberOfDownloadedEpisodes() { + public final int getNumberOfDownloadedEpisodes() { final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA + - " WHERE " + KEY_DOWNLOADED + " > 0"; + " WHERE " + KEY_DOWNLOADED + " > 0"; - Cursor c = db.rawQuery(query, null); + Cursor c = db.rawQuery(query, null); int result = 0; if (c.moveToFirst()) { - result = c.getInt(0); + result = c.getInt(0); } - c.close(); - return result; - } - - /** - * Uses DatabaseUtils to escape a search query and removes ' at the - * beginning and the end of the string returned by the escape method. - */ - private String prepareSearchQuery(String query) { - StringBuilder builder = new StringBuilder(); - DatabaseUtils.appendEscapedSQLString(builder, query); - builder.deleteCharAt(0); - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); - } - - /** - * Searches for the given query in the description of all items or the items - * of a specified feed. - * - * @return A cursor with all search results in SEL_FI_EXTRA selection. - * */ - public Cursor searchItemDescriptions(long feedID, String query) { - if (feedID != 0) { - // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED - + "=? AND " + KEY_DESCRIPTION + " LIKE '%" - + prepareSearchQuery(query) + "%'", - new String[] { String.valueOf(feedID) }, null, null, - null); - } else { - // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, - KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query) - + "%'", null, null, null, null); - } - } - - /** - * Searches for the given query in the content-encoded field of all items or - * the items of a specified feed. - * - * @return A cursor with all search results in SEL_FI_EXTRA selection. - * */ - public Cursor searchItemContentEncoded(long feedID, String query) { - if (feedID != 0) { - // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED - + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%" - + prepareSearchQuery(query) + "%'", - new String[] { String.valueOf(feedID) }, null, null, - null); - } else { - // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, - KEY_CONTENT_ENCODED + " LIKE '%" - + prepareSearchQuery(query) + "%'", null, null, - null, null); - } - } + c.close(); + return result; + } + + /** + * Uses DatabaseUtils to escape a search query and removes ' at the + * beginning and the end of the string returned by the escape method. + */ + private String prepareSearchQuery(String query) { + StringBuilder builder = new StringBuilder(); + DatabaseUtils.appendEscapedSQLString(builder, query); + builder.deleteCharAt(0); + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } + + /** + * Searches for the given query in the description of all items or the items + * of a specified feed. + * + * @return A cursor with all search results in SEL_FI_EXTRA selection. + */ + public Cursor searchItemDescriptions(long feedID, String query) { + if (feedID != 0) { + // search items in specific feed + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + + "=? AND " + KEY_DESCRIPTION + " LIKE '%" + + prepareSearchQuery(query) + "%'", + new String[]{String.valueOf(feedID)}, null, null, + null); + } else { + // search through all items + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, + KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query) + + "%'", null, null, null, null); + } + } + + /** + * Searches for the given query in the content-encoded field of all items or + * the items of a specified feed. + * + * @return A cursor with all search results in SEL_FI_EXTRA selection. + */ + public Cursor searchItemContentEncoded(long feedID, String query) { + if (feedID != 0) { + // search items in specific feed + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%" + + prepareSearchQuery(query) + "%'", + new String[]{String.valueOf(feedID)}, null, null, + null); + } else { + // search through all items + return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, + KEY_CONTENT_ENCODED + " LIKE '%" + + prepareSearchQuery(query) + "%'", null, null, + null, null); + } + } public Cursor searchItemTitles(long feedID, String query) { if (feedID != 0) { @@ -948,7 +960,7 @@ public class PodDBAdapter { return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED + "=? AND " + KEY_TITLE + " LIKE '%" + prepareSearchQuery(query) + "%'", - new String[] { String.valueOf(feedID) }, null, null, + new String[]{String.valueOf(feedID)}, null, null, null); } else { // search through all items @@ -962,109 +974,131 @@ public class PodDBAdapter { public Cursor searchItemChapters(long feedID, String searchQuery) { final String query; if (feedID != 0) { - query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + - TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + - TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + - feedID + " AND "+ TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" - + prepareSearchQuery(searchQuery) + "%'"; + query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + + TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + + feedID + " AND " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" + + prepareSearchQuery(searchQuery) + "%'"; } else { - query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + - TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + + query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + + TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" + prepareSearchQuery(searchQuery) + "%'"; } return db.rawQuery(query, null); } - /** Helper class for opening the Antennapod database. */ - private static class PodDBHelper extends SQLiteOpenHelper { - /** - * Constructor. - * - * @param context - * Context to use - * @param name - * Name of the database - * @param factory - * to use for creating cursor objects - * @param version - * number of the database - * */ - public PodDBHelper(final Context context, final String name, - final CursorFactory factory, final int version) { - super(context, name, factory, version); - } - - @Override - public void onCreate(final SQLiteDatabase db) { - db.execSQL(CREATE_TABLE_FEEDS); - db.execSQL(CREATE_TABLE_FEED_ITEMS); - db.execSQL(CREATE_TABLE_FEED_IMAGES); - db.execSQL(CREATE_TABLE_FEED_MEDIA); - db.execSQL(CREATE_TABLE_DOWNLOAD_LOG); - db.execSQL(CREATE_TABLE_QUEUE); - db.execSQL(CREATE_TABLE_SIMPLECHAPTERS); - } - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, - final int newVersion) { - Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " - + newVersion + "."); - if (oldVersion <= 1) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_TYPE + " TEXT"); - } - if (oldVersion <= 2) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_LINK + " TEXT"); - } - if (oldVersion <= 3) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 4) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_FEED_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 5) { - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); - } - if (oldVersion <= 6) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); + public static final int IDX_FEEDSTATISTICS_FEED = 0; + public static final int IDX_FEEDSTATISTICS_NUM_ITEMS = 1; + public static final int IDX_FEEDSTATISTICS_NEW_ITEMS = 2; + public static final int IDX_FEEDSTATISTICS_LATEST_EPISODE = 3; + public static final int IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES = 4; + + /** + * Select number of items, new items, the date of the latest episode and the number of episodes in progress. The result + * is sorted by the title of the feed. + */ + private static final String FEED_STATISTICS_QUERY = "SELECT feed, num_items, new_items, latest_episode, in_progress FROM " + + "(SELECT feed,count(*) AS num_items," + + " COUNT(CASE WHEN read=0 THEN 1 END) AS new_items," + + " MAX(pubDate) AS latest_episode," + + " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," + + " COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " + + " FROM FeedItems INNER JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" + + " INNER JOIN Feeds ON Feeds.id = feed ORDER BY Feeds.title;"; + + public Cursor getFeedStatisticsCursor() { + return db.rawQuery(FEED_STATISTICS_QUERY, null); + } + + /** + * Helper class for opening the Antennapod database. + */ + private static class PodDBHelper extends SQLiteOpenHelper { + /** + * Constructor. + * + * @param context Context to use + * @param name Name of the database + * @param factory to use for creating cursor objects + * @param version number of the database + */ + public PodDBHelper(final Context context, final String name, + final CursorFactory factory, final int version) { + super(context, name, factory, version); } - if (oldVersion <= 7) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE - + " INTEGER"); + + @Override + public void onCreate(final SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_FEEDS); + db.execSQL(CREATE_TABLE_FEED_ITEMS); + db.execSQL(CREATE_TABLE_FEED_IMAGES); + db.execSQL(CREATE_TABLE_FEED_MEDIA); + db.execSQL(CREATE_TABLE_DOWNLOAD_LOG); + db.execSQL(CREATE_TABLE_QUEUE); + db.execSQL(CREATE_TABLE_SIMPLECHAPTERS); } - if (oldVersion <= 8) { - final int KEY_ID_POSITION = 0; - final int KEY_MEDIA_POSITION = 1; - - // Add feeditem column to feedmedia table - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_FEEDITEM - + " INTEGER"); - Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[] {KEY_MEDIA}, null, null, null); - if (feeditemCursor.moveToFirst()) { - db.beginTransaction(); - ContentValues contentValues = new ContentValues(); - do { - long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); - contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); - db.update(TABLE_NAME_FEED_MEDIA, contentValues, "?=?", new String[] {KEY_ID, Long.toString(mediaId)}); - contentValues.clear(); - } while (feeditemCursor.moveToNext()); - db.setTransactionSuccessful(); - db.endTransaction(); + + @Override + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, + final int newVersion) { + Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " + + newVersion + "."); + if (oldVersion <= 1) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " + + KEY_TYPE + " TEXT"); + } + if (oldVersion <= 2) { + db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + KEY_LINK + " TEXT"); + } + if (oldVersion <= 3) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 4) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " + + KEY_FEED_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 5) { + db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); + } + if (oldVersion <= 6) { + db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); + } + if (oldVersion <= 7) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE + + " INTEGER"); + } + if (oldVersion <= 8) { + final int KEY_ID_POSITION = 0; + final int KEY_MEDIA_POSITION = 1; + + // Add feeditem column to feedmedia table + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_FEEDITEM + + " INTEGER"); + Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[]{KEY_MEDIA}, null, null, null); + if (feeditemCursor.moveToFirst()) { + db.beginTransaction(); + ContentValues contentValues = new ContentValues(); + do { + long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); + contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); + db.update(TABLE_NAME_FEED_MEDIA, contentValues, "?=?", new String[]{KEY_ID, String.valueOf(mediaId)}); + contentValues.clear(); + } while (feeditemCursor.moveToNext()); + db.setTransactionSuccessful(); + db.endTransaction(); + } + feeditemCursor.close(); } - feeditemCursor.close(); } } - } } -- cgit v1.2.3 From ca795a3fdc109804ddc73f65e57584a94812bf8b Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Mon, 5 Aug 2013 01:30:08 +0200 Subject: Removed FeedManager DBReader, DBTasks and DBWriter should be used instead from now on --- src/de/danoeh/antennapod/storage/FeedSearcher.java | 224 --------------------- 1 file changed, 224 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/FeedSearcher.java b/src/de/danoeh/antennapod/storage/FeedSearcher.java index a16430056..e7aa93f83 100644 --- a/src/de/danoeh/antennapod/storage/FeedSearcher.java +++ b/src/de/danoeh/antennapod/storage/FeedSearcher.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.storage; import android.content.Context; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.SearchResult; import de.danoeh.antennapod.util.comparator.SearchResultValueComparator; @@ -55,227 +54,4 @@ public class FeedSearcher { Collections.sort(result, new SearchResultValueComparator()); return result; } - /* - *//** Performs a search in all feeds or one specific feed. *//* - public static ArrayList performSearch(final Context context, - final String query, final Feed selectedFeed) { - final String lcQuery = query.toLowerCase(); - final ArrayList result = new ArrayList(); - if (selectedFeed == null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Performing global search"); - if (AppConfig.DEBUG) - Log.d(TAG, "Searching Feed titles"); - searchFeedtitles(lcQuery, result); - } else if (AppConfig.DEBUG) { - Log.d(TAG, "Performing search on specific feed"); - } - - if (AppConfig.DEBUG) - Log.d(TAG, "Searching Feeditem titles"); - searchFeedItemTitles(lcQuery, result, selectedFeed); - - if (AppConfig.DEBUG) - Log.d(TAG, "Searching item-chaptertitles"); - searchFeedItemChapters(lcQuery, result, selectedFeed); - - Looper.prepare(); - DBTasks.searchFeedItemDescription(context, selectedFeed, lcQuery, - new DBTasks.QueryTaskCallback() { - - @Override - public void handleResult(Cursor cResult) { - searchFeedItemContentEncodedCursor(context, lcQuery, result, - selectedFeed, cResult); - - } - - @Override - public void onCompletion() { - DBTasks.searchFeedItemContentEncoded(context, - selectedFeed, lcQuery, - new DBTasks.QueryTaskCallback() { - - @Override - public void handleResult(Cursor cResult) { - searchFeedItemDescriptionCursor(context, - lcQuery, result, selectedFeed, - cResult); - } - - @Override - public void onCompletion() { - Looper.myLooper().quit(); - } - }); - } - }); - - Looper.loop(); - if (AppConfig.DEBUG) - Log.d(TAG, "Sorting results"); - Collections.sort(result, new SearchResultValueComparator()); - - return result; - } - - private static void searchFeedtitles(String query, - ArrayList destination) { - FeedManager manager = FeedManager.getInstance(); - for (Feed feed : manager.getFeeds()) { - SearchResult result = createSearchResult(feed, query, feed - .getTitle().toLowerCase(), VALUE_FEED_TITLE); - if (result != null) { - destination.add(result); - } - } - } - - private static void searchFeedItemTitles(String query, - ArrayList destination, Feed selectedFeed) { - FeedManager manager = FeedManager.getInstance(); - if (selectedFeed == null) { - for (Feed feed : manager.getFeeds()) { - searchFeedItemTitlesSingleFeed(query, destination, feed); - } - } else { - searchFeedItemTitlesSingleFeed(query, destination, selectedFeed); - } - } - - private static void searchFeedItemTitlesSingleFeed(String query, - ArrayList destination, Feed feed) { - for (FeedItem item : feed.getItems()) { - SearchResult result = createSearchResult(item, query, item - .getTitle().toLowerCase(), VALUE_ITEM_TITLE); - if (result != null) { - result.setSubtitle(PodcastApp.getInstance().getString( - R.string.found_in_title_label)); - destination.add(result); - } - - } - } - - private static void searchFeedItemChapters(String query, - ArrayList destination, Feed selectedFeed) { - if (selectedFeed == null) { - for (Feed feed : manager.getFeeds()) { - searchFeedItemChaptersSingleFeed(query, destination, feed); - } - } else { - searchFeedItemChaptersSingleFeed(query, destination, selectedFeed); - } - } - - private static void searchFeedItemChaptersSingleFeed(String query, - ArrayList destination, Feed feed) { - for (FeedItem item : feed.getItems()) { - if (item.getChapters() != null) { - for (Chapter sc : item.getChapters()) { - SearchResult result = createSearchResult(item, query, sc - .getTitle().toLowerCase(), VALUE_ITEM_CHAPTER); - if (result != null) { - result.setSubtitle(PodcastApp.getInstance().getString( - R.string.found_in_chapters_label)); - destination.add(result); - } - } - } - } - } - - private static void searchFeedItemDescriptionCursor(Context context, String query, - ArrayList destination, Feed feed, Cursor cursor) { - FeedManager manager = FeedManager.getInstance(); - List items = DBReader.extractItemlistFromCursor(cursor); - if (cursor.moveToFirst()) { - do { - final long itemId = cursor - .getLong(PodDBAdapter.IDX_FI_EXTRA_ID); - String content = cursor - .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION); - if (content != null) { - content = content.toLowerCase(); - final long feedId = cursor - .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED); - FeedItem item = null; - if (feed == null) { - item = manager.getFeedItem(itemId, feedId); - } else { - item = manager.getFeedItem(itemId, feed); - } - if (item != null) { - SearchResult searchResult = createSearchResult(item, - query, content, VALUE_ITEM_DESCRIPTION); - if (searchResult != null) { - searchResult.setSubtitle(PodcastApp.getInstance() - .getString( - R.string.found_in_shownotes_label)); - destination.add(searchResult); - - } - } - } - - } while (cursor.moveToNext()); - } - } - - private static void searchFeedItemContentEncodedCursor(Context context, String query, - ArrayList destination, Feed feed, Cursor cursor) { - if (cursor.moveToFirst()) { - do { - final long itemId = cursor - .getLong(PodDBAdapter.IDX_FI_EXTRA_ID); - String content = cursor - .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED); - if (content != null) { - content = content.toLowerCase(); - - final long feedId = cursor - .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED); - FeedItem item = null; - if (feed == null) { - item = manager.getFeedItem(itemId, feedId); - } else { - item = manager.getFeedItem(itemId, feed); - } - if (item != null) { - SearchResult searchResult = createSearchResult(item, - query, content, VALUE_ITEM_DESCRIPTION); - if (searchResult != null) { - searchResult.setSubtitle(PodcastApp.getInstance() - .getString( - R.string.found_in_shownotes_label)); - destination.add(searchResult); - } - } - } - } while (cursor.moveToNext()); - } - } - - private static SearchResult createSearchResult(FeedComponent component, - String query, String text, int baseValue) { - int bonus = 0; - boolean found = false; - // try word search - Pattern word = Pattern.compile("\b" + query + "\b"); - Matcher matcher = word.matcher(text); - found = matcher.find(); - if (found) { - bonus = VALUE_WORD_MATCH; - } else { - // search for other occurence - found = text.contains(query); - } - - if (found) { - return new SearchResult(component, baseValue + bonus); - } else { - return null; - } - }*/ - } -- cgit v1.2.3 From a90c7219f363e9b70159b42e3131d49d58561f4b Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Mon, 5 Aug 2013 15:01:31 +0200 Subject: Refresh lock was used incorrectly --- src/de/danoeh/antennapod/storage/DBTasks.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index bf615776d..a3964283c 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import android.content.Context; @@ -73,11 +74,10 @@ public final class DBTasks { } } - private static ReentrantLock refreshAllFeedsLock = new ReentrantLock(); - + private static AtomicBoolean isRefreshing = new AtomicBoolean(false); public static void refreshAllFeeds(final Context context, final List feeds) { - if (refreshAllFeedsLock.tryLock()) { + if (isRefreshing.compareAndSet(false, true)) { new Thread() { public void run() { if (feeds != null) { @@ -85,7 +85,7 @@ public final class DBTasks { } else { refreshFeeds(context, DBReader.getFeedList(context)); } - refreshAllFeedsLock.unlock(); + isRefreshing.set(false); } }.start(); } else { -- cgit v1.2.3 From 03804d5d51a6dc415ccbf3dabf7ecfd4d19c0d4b Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Tue, 6 Aug 2013 19:17:17 +0200 Subject: Several bugfixes in database queries --- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 5718e03c0..0238e7697 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -473,8 +473,7 @@ public class PodDBAdapter { if (resetMediaPosition) { values.clear(); values.put(KEY_POSITION, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, "?=?", new String[]{ - KEY_ID, String.valueOf(mediaId)}); + db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); } db.setTransactionSuccessful(); @@ -487,8 +486,7 @@ public class PodDBAdapter { for (long id : itemIds) { values.clear(); values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, "?=?", new String[]{ - KEY_ID, String.valueOf(id)}); + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)}); } db.setTransactionSuccessful(); db.endTransaction(); @@ -793,7 +791,7 @@ public class PodDBAdapter { } open(); Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, - KEY_PLAYBACK_COMPLETION_DATE + " IS NOT NULL", null, null, + KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null, null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit); return c; } @@ -1091,7 +1089,7 @@ public class PodDBAdapter { do { long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); - db.update(TABLE_NAME_FEED_MEDIA, contentValues, "?=?", new String[]{KEY_ID, String.valueOf(mediaId)}); + db.update(TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); contentValues.clear(); } while (feeditemCursor.moveToNext()); db.setTransactionSuccessful(); -- cgit v1.2.3 From 24fdbba98b29b1988d2240d1c015c0a25db10ecb Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Thu, 8 Aug 2013 12:48:39 +0200 Subject: Added documentation to DBReader --- src/de/danoeh/antennapod/storage/DBReader.java | 147 ++++++++++++++++++++- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 6 +- 2 files changed, 149 insertions(+), 4 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 4f08c2b00..71d11f13b 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -23,12 +23,36 @@ import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; +/** + * 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. + */ public final class DBReader { private static final String TAG = "DBReader"; + /** + * Maximum size of the list returned by {@link #getPlaybackHistory(android.content.Context)}. + */ + public static final int PLAYBACK_HISTORY_SIZE = 50; + + /** + * Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}. + */ + public static final int DOWNLOAD_LOG_SIZE = 200; + + private DBReader() { } + /** + * Returns a list of Feeds, sorted alphabetically by their title. + * + * @param context A context that is used for opening a database connection. + * @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(android.content.Context, de.danoeh.antennapod.feed.Feed)}. + */ public static List getFeedList(final Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting Feedlist"); @@ -49,6 +73,16 @@ public final class DBReader { return feeds; } + /** + * Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time. + * + * @param context A context that is used for opening a database connection. + * @param expirationTime Time that is used for determining whether a feed is outdated or not. + * A Feed is considered expired if 'lastUpdate < (currentTime - expirationTime)' evaluates to true. + * @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(android.content.Context, de.danoeh.antennapod.feed.Feed)}. + */ static List getExpiredFeedsList(final Context context, final long expirationTime) { if (AppConfig.DEBUG) Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); @@ -69,6 +103,12 @@ public final class DBReader { return feeds; } + /** + * Takes a list of FeedItems and loads their corresponding Feed-objects from the database. + * + * @param context A context that is used for opening a database connection. + * @param items The FeedItems whose Feed-objects should be loaded. + */ public static void loadFeedDataOfFeedItemlist(Context context, List items) { List feeds = getFeedList(context); @@ -85,6 +125,15 @@ public final class DBReader { } } + /** + * 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(android.content.Context)} instead. + * + * @param context A context that is used for opening a database connection. + * @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 getFeedItemList(Context context, final Feed feed) { if (AppConfig.DEBUG) @@ -290,6 +339,14 @@ public final class DBReader { return items; } + /** + * Loads the IDs of the FeedItems in the queue. This method should be preferred over + * {@link #getQueue(android.content.Context)} if the FeedItems of the queue are not needed. + * + * @param context A context that is used for opening a database connection. + * @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.util.QueueAccess} object for easier access to the queue's properties. + */ public static List getQueueIDList(Context context) { PodDBAdapter adapter = new PodDBAdapter(context); @@ -313,6 +370,15 @@ public final class DBReader { 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(android.content.Context)} instead. + * + * @param context A context that is used for opening a database connection. + * @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.util.QueueAccess} object for easier access to the queue's properties. + */ public static List getQueue(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting queue"); @@ -324,6 +390,12 @@ public final class DBReader { return items; } + /** + * Loads a list of FeedItems whose episode has been downloaded. + * + * @param context A context that is used for opening a database connection. + * @return A list of FeedItems whose episdoe has been downloaded. + */ public static List getDownloadedItems(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting downloaded items"); @@ -343,6 +415,13 @@ public final class DBReader { } + /** + * Loads a list of FeedItems whose 'read'-attribute is set to false. + * + * @param context A context that is used for opening a database connection. + * @return A list of FeedItems whose 'read'-attribute it set to false. If the FeedItems in the list are not used, + * consider using {@link #getUnreadItemIds(android.content.Context)} instead. + */ public static List getUnreadItemsList(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting unread items list"); @@ -362,6 +441,13 @@ public final class DBReader { 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. + * @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred + * over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used. + */ public static long[] getUnreadItemIds(Context context) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -377,6 +463,14 @@ public final class DBReader { return itemIds; } + /** + * 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. + * + * @param context A context that is used for opening a database connection. + * @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 getPlaybackHistory(final Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Loading playback history"); @@ -400,13 +494,20 @@ public final class DBReader { return items; } + /** + * Loads the download log from the database. + * + * @param context A context that is used for opening a database connection. + * @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 getDownloadLog(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Extracting DownloadLog"); PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - Cursor logCursor = adapter.getDownloadLogCursor(); + Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE); List downloadLog = new ArrayList( logCursor.getCount()); @@ -439,6 +540,14 @@ public final class DBReader { return downloadLog; } + /** + * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over + * {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)} if only metadata about + * the FeedItems is needed. + * + * @param context A context that is used for opening a database connection. + * @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title. + */ public static List getFeedStatisticsList(final Context context) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -459,6 +568,14 @@ public final class DBReader { return result; } + /** + * Loads a specific Feed from the database. + * + * @param context A context that is used for opening a database connection. + * @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 Context context, final long feedId) { if (AppConfig.DEBUG) Log.d(TAG, "Loading feed with id " + feedId); @@ -494,6 +611,14 @@ public final class DBReader { } + /** + * Loads a specific FeedItem from the database. + * + * @param context A context that is used for opening a database connection. + * @param itemId The ID of the FeedItem + * @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes of the FeedItem will + * also be loaded from the database. + */ public static FeedItem getFeedItem(final Context context, final long itemId) { if (AppConfig.DEBUG) Log.d(TAG, "Loading feeditem with id " + itemId); @@ -506,6 +631,12 @@ public final class DBReader { } + /** + * Loads additional information about a FeedItem, e.g. shownotes + * + * @param context A context that is used for opening a database connection. + * @param item The FeedItem + */ public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -521,6 +652,12 @@ public final class DBReader { adapter.close(); } + /** + * Returns the number of downloaded episodes. + * + * @param context A context that is used for opening a database connection. + * @return The number of downloaded episodes. + */ public static int getNumberOfDownloadedEpisodes(final Context context) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -529,6 +666,12 @@ public final class DBReader { return result; } + /** + * Returns the number of unread items. + * + * @param context A context that is used for opening a database connection. + * @return The number of unread items. + */ public static int getNumberOfUnreadItems(final Context context) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -540,6 +683,7 @@ public final class DBReader { /** * Searches the DB for a FeedImage of the given id. * + * @param context A context that is used for opening a database connection. * @param imageId The id of the object * @return The found object */ @@ -577,6 +721,7 @@ public final class DBReader { /** * Searches the DB for a FeedMedia of the given id. * + * @param context A context that is used for opening a database connection. * @param mediaId The id of the object * @return The found object */ diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 0238e7697..a0c6cb11f 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -640,7 +640,7 @@ public class PodDBAdapter { public final Cursor getExpiredFeedsCursor(long expirationTime) { open(); Cursor c = db.query(TABLE_NAME_FEEDS, null, "? Date: Fri, 9 Aug 2013 21:51:13 +0200 Subject: Added documentation to DBWriter --- src/de/danoeh/antennapod/storage/DBReader.java | 2 + src/de/danoeh/antennapod/storage/DBWriter.java | 1142 +++++++++++--------- src/de/danoeh/antennapod/storage/PodDBAdapter.java | 4 +- 3 files changed, 646 insertions(+), 502 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 71d11f13b..b1ebbc45d 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -27,6 +27,8 @@ import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; * 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.feed.EventDistributor} to notify listeners about changes in the database. + */ public final class DBReader { private static final String TAG = "DBReader"; diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 3db0b8a33..4ad9e9e1f 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.storage; import java.io.File; -import java.util.Collections; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -22,517 +22,645 @@ import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.QueueAccess; +/** + * Provides methods for writing data to AntennaPod's database. + * In general, DBWriter-methods will be executed on an internal ExecutorService. + * Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's + * will NOT contain any results. + * The caller can also use the {@link EventDistributor} in order to be notified about the method's completion asynchronously. + * This class will use the {@link EventDistributor} to notify listeners about changes in the database. + */ public class DBWriter { - private static final String TAG = "DBWriter"; - - private static final ExecutorService dbExec; - static { - dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - } - - private DBWriter() { - } - - public static void deleteFeedMediaOfItem(final Context context, - final long itemId) { - dbExec.submit(new Runnable() { - @Override - public void run() { - final FeedItem item = DBReader.getFeedItem(context, itemId); - if (item != null && item.hasMedia()) { - final FeedMedia media = item.getMedia(); - boolean result = false; - if (media.isDownloaded()) { - // delete downloaded media file - File mediaFile = new File(media.getFile_url()); - if (mediaFile.exists()) { - result = mediaFile.delete(); - } - media.setDownloaded(false); - media.setFile_url(null); - setFeedMedia(context, media); - - // If media is currently being played, change playback - // type to 'stream' and shutdown playback service - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) { - if (media.getId() == PlaybackPreferences - .getCurrentlyPlayingFeedMediaId()) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - true); - editor.commit(); - } - if (PlaybackPreferences - .getCurrentlyPlayingFeedMediaId() == media - .getId()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - } - } - if (AppConfig.DEBUG) - Log.d(TAG, "Deleting File. Result: " + result); - } - } - }); - } - - public static Future deleteFeed(final Context context, final long feedId) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - DownloadRequester requester = DownloadRequester.getInstance(); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context - .getApplicationContext()); - final Feed feed = DBReader.getFeed(context, feedId); - if (feed != null) { - if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA - && PlaybackPreferences.getLastPlayedFeedId() == feed - .getId()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - -1); - editor.commit(); - } - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - // delete image file - if (feed.getImage() != null) { - if (feed.getImage().isDownloaded() - && feed.getImage().getFile_url() != null) { - File imageFile = new File(feed.getImage() - .getFile_url()); - imageFile.delete(); - } else if (requester.isDownloadingFile(feed.getImage())) { - requester.cancelDownload(context, feed.getImage()); - } - } - // delete stored media files and mark them as read - List queue = DBReader.getQueue(context); - boolean queueWasModified = false; - if (feed.getItems() == null) { - DBReader.getFeedItemList(context, feed); - } - - for (FeedItem item : feed.getItems()) { - queueWasModified |= queue.remove(item); - if (item.getMedia() != null - && item.getMedia().isDownloaded()) { - File mediaFile = new File(item.getMedia() - .getFile_url()); - mediaFile.delete(); - } else if (item.getMedia() != null - && requester.isDownloadingFile(item.getMedia())) { - requester.cancelDownload(context, item.getMedia()); - } - } - if (queueWasModified) { - adapter.setQueue(queue); - } - adapter.removeFeed(feed); - adapter.close(); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - } - }); - } - - public static void clearPlaybackHistory(final Context context) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearPlaybackHistory(); - adapter.close(); - EventDistributor.getInstance() - .sendPlaybackHistoryUpdateBroadcast(); - } - }); - } - - public static void addItemToPlaybackHistory(final Context context, - final FeedItem item) { - if (item.hasMedia() - && item.getMedia().getPlaybackCompletionDate() != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Adding new item to playback history"); - EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); - } - - } - - private static void cleanupDownloadLog(final PodDBAdapter adapter) { - final int DOWNLOAD_LOG_SIZE = 50; - final long logSize = adapter.getDownloadLogSize(); - if (logSize > DOWNLOAD_LOG_SIZE) { - if (AppConfig.DEBUG) - Log.d(TAG, "Cleaning up download log"); - adapter.removeDownloadLogItems(logSize - DOWNLOAD_LOG_SIZE); - } - } - - public static void addDownloadStatus(final Context context, - final DownloadStatus status) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - adapter.setDownloadStatus(status); - cleanupDownloadLog(adapter); - adapter.close(); - EventDistributor.getInstance().sendDownloadLogUpdateBroadcast(); - } - }); - - } - - public static void addQueueItemAt(final Context context, final long itemId, - final int index, final boolean performAutoDownload) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); - FeedItem item = null; - - if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModfied = false; - - if (!itemListContains(queue, itemId)) { - item = DBReader.getFeedItem(context, itemId); - if (item != null) { - queue.add(index, item); - queueModified = true; - if (!item.isRead()) { - item.setRead(true); - unreadItemsModfied = true; - } - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } - if (unreadItemsModfied && item != null) { - adapter.setSingleFeedItem(item); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } - } - adapter.close(); - if (performAutoDownload) { - - new Thread() { - @Override - public void run() { - DBTasks.autodownloadUndownloadedItems(context); - - } - }.start(); - } - - } - }); - - } - - public static void addQueueItem(final Context context, - final long... itemIds) { - if (itemIds.length > 0) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader.getQueue(context, - adapter); - - if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModfied = false; - List itemsToSave = new LinkedList(); - for (int i = 0; i < itemIds.length; i++) { - if (!itemListContains(queue, itemIds[i])) { - final FeedItem item = DBReader.getFeedItem( - context, itemIds[i]); - - if (item != null) { - queue.add(item); - queueModified = true; - if (!item.isRead()) { - item.setRead(true); - itemsToSave.add(item); - unreadItemsModfied = true; - } - } - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } - if (unreadItemsModfied) { - adapter.setFeedItemlist(itemsToSave); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } - } - adapter.close(); - new Thread() { - @Override - public void run() { - DBTasks.autodownloadUndownloadedItems(context); - - } - }.start(); - } - }); - } - } - - public static void clearQueue(final Context context) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearQueue(); - adapter.close(); - - EventDistributor.getInstance().sendQueueUpdateBroadcast(); - } - }); - } - - public static void removeQueueItem(final Context context, - final long itemId, final boolean performAutoDownload) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); - FeedItem item = null; - - if (queue != null) { - boolean queueModified = false; + private static final String TAG = "DBWriter"; + + private static final ExecutorService dbExec; + + static { + dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); + } + + private DBWriter() { + } + + /** + * Deletes a downloaded FeedMedia file from the storage device. + * + * @param context A context that is used for opening a database connection. + * @param mediaId ID of the FeedMedia object whose downloaded file should be deleted. + */ + public static Future deleteFeedMediaOfItem(final Context context, + final long mediaId) { + return dbExec.submit(new Runnable() { + @Override + public void run() { + + final FeedMedia media = DBReader.getFeedMedia(context, mediaId); + if (media != null) { + boolean result = false; + if (media.isDownloaded()) { + // delete downloaded media file + File mediaFile = new File(media.getFile_url()); + if (mediaFile.exists()) { + result = mediaFile.delete(); + } + media.setDownloaded(false); + media.setFile_url(null); + setFeedMedia(context, media); + + // If media is currently being played, change playback + // type to 'stream' and shutdown playback service + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) { + if (media.getId() == PlaybackPreferences + .getCurrentlyPlayingFeedMediaId()) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, + true); + editor.commit(); + } + if (PlaybackPreferences + .getCurrentlyPlayingFeedMediaId() == media + .getId()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Deleting File. Result: " + result); + } + } + }); + } + + /** + * Deletes a Feed and all downloaded files of its components like images and downloaded episodes. + * + * @param context A context that is used for opening a database connection. + * @param feedId ID of the Feed that should be deleted. + */ + public static Future deleteFeed(final Context context, final long feedId) { + return dbExec.submit(new Runnable() { + @Override + public void run() { + DownloadRequester requester = DownloadRequester.getInstance(); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context + .getApplicationContext()); + final Feed feed = DBReader.getFeed(context, feedId); + if (feed != null) { + if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA + && PlaybackPreferences.getLastPlayedFeedId() == feed + .getId()) { + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + -1); + editor.commit(); + } + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + // delete image file + if (feed.getImage() != null) { + if (feed.getImage().isDownloaded() + && feed.getImage().getFile_url() != null) { + File imageFile = new File(feed.getImage() + .getFile_url()); + imageFile.delete(); + } else if (requester.isDownloadingFile(feed.getImage())) { + requester.cancelDownload(context, feed.getImage()); + } + } + // delete stored media files and mark them as read + List queue = DBReader.getQueue(context); + boolean queueWasModified = false; + if (feed.getItems() == null) { + DBReader.getFeedItemList(context, feed); + } + + for (FeedItem item : feed.getItems()) { + queueWasModified |= queue.remove(item); + if (item.getMedia() != null + && item.getMedia().isDownloaded()) { + File mediaFile = new File(item.getMedia() + .getFile_url()); + mediaFile.delete(); + } else if (item.getMedia() != null + && requester.isDownloadingFile(item.getMedia())) { + requester.cancelDownload(context, item.getMedia()); + } + } + if (queueWasModified) { + adapter.setQueue(queue); + } + adapter.removeFeed(feed); + adapter.close(); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + } + }); + } + + /** + * Deletes the entire playback history. + * + * @param context A context that is used for opening a database connection. + */ + public static Future clearPlaybackHistory(final Context context) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.clearPlaybackHistory(); + adapter.close(); + EventDistributor.getInstance() + .sendPlaybackHistoryUpdateBroadcast(); + } + }); + } + + /** + * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if + * its playback completion date is set to a non-null value. This method will set the playback completion date to the + * current date regardless of the current value. + * + * @param context A context that is used for opening a database connection. + * @param media FeedMedia that should be added to the playback history. + */ + public static Future addItemToPlaybackHistory(final Context context, + final FeedMedia media) { + return dbExec.submit(new Runnable() { + @Override + public void run() { + if (AppConfig.DEBUG) + Log.d(TAG, "Adding new item to playback history"); + media.setPlaybackCompletionDate(new Date()); + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setMedia(media); + adapter.close(); + EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); + + } + }); + } + + private static void cleanupDownloadLog(final PodDBAdapter adapter) { + final long logSize = adapter.getDownloadLogSize(); + if (logSize > DBReader.DOWNLOAD_LOG_SIZE) { + if (AppConfig.DEBUG) + Log.d(TAG, "Cleaning up download log"); + adapter.removeDownloadLogItems(logSize - DBReader.DOWNLOAD_LOG_SIZE); + } + } + + /** + * Adds a Download status object to the download log. + * + * @param context A context that is used for opening a database connection. + * @param status The DownloadStatus object. + */ + public static Future addDownloadStatus(final Context context, + final DownloadStatus status) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + adapter.setDownloadStatus(status); + cleanupDownloadLog(adapter); + adapter.close(); + EventDistributor.getInstance().sendDownloadLogUpdateBroadcast(); + } + }); + + } + + /** + * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to + * true. If the FeedItem is already in the queue, the queue will not be modified. + * + * @param context A context that is used for opening a database connection. + * @param itemId ID of the FeedItem that should be added to the queue. + * @param index Destination index. Must be in range 0..queue.size() + * @param performAutoDownload True if an auto-download process should be started after the operation + * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size() + */ + public static Future addQueueItemAt(final Context context, final long itemId, + final int index, final boolean performAutoDownload) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); + FeedItem item = null; + + if (queue != null) { + boolean queueModified = false; + boolean unreadItemsModified = false; + + if (!itemListContains(queue, itemId)) { + item = DBReader.getFeedItem(context, itemId); + if (item != null) { + queue.add(index, item); + queueModified = true; + if (!item.isRead()) { + item.setRead(true); + unreadItemsModified = true; + } + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + if (unreadItemsModified && item != null) { + adapter.setSingleFeedItem(item); + EventDistributor.getInstance() + .sendUnreadItemsUpdateBroadcast(); + } + } + adapter.close(); + if (performAutoDownload) { + + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); + + } + }.start(); + } + + } + }); + + } + + /** + * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true. + * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue. + * + * @param context A context that is used for opening a database connection. + * @param itemIds IDs of the FeedItem objects that should be added to the queue. + */ + public static Future addQueueItem(final Context context, + final long... itemIds) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + if (itemIds.length > 0) { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader.getQueue(context, + adapter); + + if (queue != null) { + boolean queueModified = false; + boolean unreadItemsModified = false; + List itemsToSave = new LinkedList(); + for (int i = 0; i < itemIds.length; i++) { + if (!itemListContains(queue, itemIds[i])) { + final FeedItem item = DBReader.getFeedItem( + context, itemIds[i]); + + if (item != null) { + queue.add(item); + queueModified = true; + if (!item.isRead()) { + item.setRead(true); + itemsToSave.add(item); + unreadItemsModified = true; + } + } + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } + if (unreadItemsModified) { + adapter.setFeedItemlist(itemsToSave); + EventDistributor.getInstance() + .sendUnreadItemsUpdateBroadcast(); + } + } + adapter.close(); + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); + + } + }.start(); + } + } + }); + + } + + /** + * Removes all FeedItem objects from the queue. + * + * @param context A context that is used for opening a database connection. + */ + public static Future clearQueue(final Context context) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.clearQueue(); + adapter.close(); + + EventDistributor.getInstance().sendQueueUpdateBroadcast(); + } + }); + } + + /** + * Removes a FeedItem object from the queue. + * + * @param context A context that is used for opening a database connection. + * @param itemId ID of the FeedItem that should be removed. + * @param performAutoDownload true if an auto-download process should be started after the operation. + */ + public static Future removeQueueItem(final Context context, + final long itemId, final boolean performAutoDownload) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); + FeedItem item = null; + + if (queue != null) { + boolean queueModified = false; QueueAccess queueAccess = QueueAccess.ItemListAccess(queue); if (queueAccess.contains(itemId)) { - item = DBReader.getFeedItem(context, itemId); - if (item != null) { - queueModified = queueAccess.remove(itemId); - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } else { + item = DBReader.getFeedItem(context, itemId); + if (item != null) { + queueModified = queueAccess.remove(itemId); + } + } + if (queueModified) { + adapter.setQueue(queue); + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } else { Log.w(TAG, "Queue was not modified by call to removeQueueItem"); } - } else { + } else { Log.e(TAG, "removeQueueItem: Could not load queue"); } - adapter.close(); - if (performAutoDownload) { + adapter.close(); + if (performAutoDownload) { - new Thread() { - @Override - public void run() { - DBTasks.autodownloadUndownloadedItems(context); + new Thread() { + @Override + public void run() { + DBTasks.autodownloadUndownloadedItems(context); - } - }.start(); - } - } - }); + } + }.start(); + } + } + }); - } + } - public static void moveQueueItem(final Context context, final int from, - final int to, final boolean broadcastUpdate) { - dbExec.submit(new Runnable() { + /** + * Changes the position of a FeedItem in the queue. + * + * @param context A context that is used for opening a database connection. + * @param from Source index. Must be in range 0..queue.size()-1. + * @param to Destination index. Must be in range 0..queue.size()-1. + * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to + * false if the caller wants to avoid unexpected updates of the GUI. + * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) + */ + public static Future moveQueueItem(final Context context, final int from, + final int to, final boolean broadcastUpdate) { + return dbExec.submit(new Runnable() { - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final List queue = DBReader + .getQueue(context, adapter); - if (queue != null) { - if (from >= 0 && from < queue.size() && to >= 0 - && to < queue.size()) { + if (queue != null) { + if (from >= 0 && from < queue.size() && to >= 0 + && to < queue.size()) { - final FeedItem item = queue.remove(from); - queue.add(to, item); + final FeedItem item = queue.remove(from); + queue.add(to, item); adapter.setQueue(queue); - if (broadcastUpdate) { - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } + if (broadcastUpdate) { + EventDistributor.getInstance() + .sendQueueUpdateBroadcast(); + } - } - } else { + } + } else { Log.e(TAG, "moveQueueItem: Could not load queue"); } - adapter.close(); - } - }); - } + adapter.close(); + } + }); + } - public static void markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) { + /** + * Sets the 'read'-attribute of a FeedItem to the specified value. + * + * @param context A context that is used for opening a database connection. + * @param item The FeedItem object + * @param read New value of the 'read'-attribute + * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object. + * If the FeedItem has no FeedMedia object, this parameter will be ignored. + */ + public static Future markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) { long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0; - markItemRead(context, item.getId(), read, mediaId, resetMediaPosition); + return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition); + } + + /** + * Sets the 'read'-attribute of a FeedItem to the specified value. + * + * @param context A context that is used for opening a database connection. + * @param itemId ID of the FeedItem + * @param read New value of the 'read'-attribute + */ + public static Future markItemRead(final Context context, final long itemId, + final boolean read) { + return markItemRead(context, itemId, read, 0, false); } - public static void markItemRead(final Context context, final long itemId, - final boolean read) { - markItemRead(context, itemId, read, 0, false); - } - - public static void markItemRead(final Context context, final long itemId, - final boolean read, final long mediaId, - final boolean resetMediaPosition) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedItemRead(read, itemId, mediaId, - resetMediaPosition); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - } - - public static void markFeedRead(final Context context, final long feedId) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId); - long[] itemIds = new long[itemCursor.getCount()]; - itemCursor.moveToFirst(); - for (int i = 0; i < itemIds.length; i++) { - itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - } - itemCursor.close(); - adapter.setFeedItemRead(true, itemIds); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - - } - - public static void markAllItemsRead(final Context context) { - dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemCursor = adapter.getUnreadItemsCursor(); - long[] itemIds = new long[itemCursor.getCount()]; - itemCursor.moveToFirst(); - for (int i = 0; i < itemIds.length; i++) { - itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - } - itemCursor.close(); - adapter.setFeedItemRead(true, itemIds); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - - } - - static Future addNewFeed(final Context context, final Feed feed) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feed); - adapter.close(); - - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - }); - } - - static Future setCompleteFeed(final Context context, final Feed feed) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feed); - adapter.close(); - - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - }}); - - } - - public static Future setFeedMedia(final Context context, - final FeedMedia media) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setMedia(media); - adapter.close(); - }}); - } + private static Future markItemRead(final Context context, final long itemId, + final boolean read, final long mediaId, + final boolean resetMediaPosition) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedItemRead(read, itemId, mediaId, + resetMediaPosition); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + } + /** + * Sets the 'read'-attribute of all FeedItems of a specific Feed to true. + * + * @param context A context that is used for opening a database connection. + * @param feedId ID of the Feed. + */ + public static Future markFeedRead(final Context context, final long feedId) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId); + long[] itemIds = new long[itemCursor.getCount()]; + itemCursor.moveToFirst(); + for (int i = 0; i < itemIds.length; i++) { + itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + } + itemCursor.close(); + adapter.setFeedItemRead(true, itemIds); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + + } + + /** + * Sets the 'read'-attribute of all FeedItems to true. + * + * @param context A context that is used for opening a database connection. + */ + public static Future markAllItemsRead(final Context context) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor itemCursor = adapter.getUnreadItemsCursor(); + long[] itemIds = new long[itemCursor.getCount()]; + itemCursor.moveToFirst(); + for (int i = 0; i < itemIds.length; i++) { + itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); + } + itemCursor.close(); + adapter.setFeedItemRead(true, itemIds); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + + } + + static Future addNewFeed(final Context context, final Feed feed) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + }); + } + + static Future setCompleteFeed(final Context context, final Feed feed) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + } + }); + + } + + /** + * Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The + * contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved. + * + * @param context A context that is used for opening a database connection. + * @param media The FeedMedia object. + */ + public static Future setFeedMedia(final Context context, + final FeedMedia media) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setMedia(media); + adapter.close(); + } + }); + } + + /** + * Saves only value of the 'position'-attribute of a FeedMedia object. + * + * @param context A context that is used for opening a database connection. + * @param media The FeedMedia object. + */ public static Future setFeedMediaPosition(final Context context, final FeedMedia media) { - return dbExec.submit(new Runnable(){ + return dbExec.submit(new Runnable() { @Override public void run() { PodDBAdapter adapter = new PodDBAdapter(context); @@ -543,8 +671,15 @@ public class DBWriter { }); } + /** + * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including + * the content of FeedComponent-attributes. + * + * @param context A context that is used for opening a database connection. + * @param item The FeedItem object. + */ public static Future setFeedItem(final Context context, - final FeedItem item) { + final FeedItem item) { return dbExec.submit(new Runnable() { @Override @@ -553,11 +688,19 @@ public class DBWriter { adapter.open(); adapter.setSingleFeedItem(item); adapter.close(); - }}); + } + }); } + /** + * Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The + * contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved. + * + * @param context A context that is used for opening a database connection. + * @param image The FeedImage object. + */ public static Future setFeedImage(final Context context, - final FeedImage image) { + final FeedImage image) { return dbExec.submit(new Runnable() { @Override @@ -566,15 +709,16 @@ public class DBWriter { adapter.open(); adapter.setImage(image); adapter.close(); - }}); + } + }); } - private static boolean itemListContains(List items, long itemId) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return true; - } - } - return false; - } + private static boolean itemListContains(List items, long itemId) { + for (FeedItem item : items) { + if (item.getId() == itemId) { + return true; + } + } + return false; + } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index a0c6cb11f..ac9309674 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -438,7 +438,7 @@ public class PodDBAdapter { } values.put(KEY_PUBDATE, item.getPubDate().getTime()); values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); - if (item.getFeed().getId() == 0) { + if (item.getFeed() != null) { setFeed(item.getFeed()); } values.put(KEY_FEED, item.getFeed().getId()); @@ -452,9 +452,7 @@ public class PodDBAdapter { new String[]{String.valueOf(item.getId())}); } if (item.getMedia() != null) { - if (item.getMedia().getId() == 0) { setMedia(item.getMedia()); - } } if (item.getChapters() != null) { setChapters(item); -- cgit v1.2.3 From 26fad34b6501adba7720c8728732d18bc613472b Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 10 Aug 2013 15:24:49 +0200 Subject: Added documentation to DBTasks --- src/de/danoeh/antennapod/storage/DBTasks.java | 137 +++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 15 deletions(-) (limited to 'src/de/danoeh/antennapod/storage') diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index a3964283c..b1efda658 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -11,12 +11,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantLock; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.os.Handler; import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.EventDistributor; @@ -32,12 +30,30 @@ import de.danoeh.antennapod.util.NetworkUtils; import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; +/** + * Provides methods for doing common tasks that use DBReader and DBWriter. + */ public final class DBTasks { private static final String TAG = "DBTasks"; private DBTasks() { } + /** + * Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to + * start the {@link PlaybackService}. + * + * @param context Used for sending starting Services and Activities. + * @param media The FeedMedia object. + * @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity} + * or {@link de.danoeh.antennapod.activity.VideoplayerActivity} + * @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as + * soon as the PlaybackService has finished loading the FeedMedia object's file. + * @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file + * will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be + * found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be + * corrected. + */ public static void playMedia(final Context context, final FeedMedia media, boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { try { @@ -59,7 +75,7 @@ public final class DBTasks { true); context.startService(launchIntent); if (showPlayer) { - // Launch Mediaplayer + // Launch media player context.startActivity(PlaybackService.getPlayerActivityIntent( context, media)); } @@ -75,6 +91,14 @@ public final class DBTasks { } private static AtomicBoolean isRefreshing = new AtomicBoolean(false); + + /** + * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still + * enqueuing Feeds for download from a previous call + * + * @param context Might be used for accessing the database + * @param feeds List of Feeds that should be refreshed. + */ public static void refreshAllFeeds(final Context context, final List feeds) { if (isRefreshing.compareAndSet(false, true)) { @@ -95,6 +119,12 @@ public final class DBTasks { } } + /** + * Refreshes expired Feeds in the list returned by the getExpiredFeedsList(Context, long) method in DBReader. + * The expiration date parameter is determined by the update interval specified in {@link UserPreferences}. + * + * @param context Used for DB access. + */ public static void refreshExpiredFeeds(final Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Refreshing expired feeds"); @@ -138,7 +168,10 @@ public final class DBTasks { } /** - * Updates a specific feed. + * Updates a specific Feed. + * + * @param context Used for requesting the download. + * @param feed The Feed object. */ public static void refreshFeed(Context context, Feed feed) throws DownloadRequestException { @@ -146,10 +179,16 @@ public final class DBTasks { new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); } + /** + * Notifies the database about a missing FeedImage file. This method will attempt to re-download the file. + * + * @param context Used for requesting the download. + * @param image The FeedImage object. + */ public static void notifyInvalidImageFile(final Context context, final FeedImage image) { Log.i(TAG, - "The feedmanager was notified about an invalid image download. It will now try to redownload the image file"); + "The DB was notified about an invalid image download. It will now try to re-download the image file"); try { DownloadRequester.getInstance().downloadImage(context, image); } catch (DownloadRequestException e) { @@ -158,6 +197,10 @@ public final class DBTasks { } } + /** + * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the + * DB and send a FeedUpdateBroadcast. + */ public static void notifyMissingFeedMediaFile(final Context context, final FeedMedia media) { Log.i(TAG, @@ -168,6 +211,11 @@ public final class DBTasks { EventDistributor.getInstance().sendFeedUpdateBroadcast(); } + /** + * Request the download of all objects in the queue. from a separate Thread. + * + * @param context Used for requesting the download an accessing the database. + */ public static void downloadAllItemsInQueue(final Context context) { new Thread() { public void run() { @@ -184,6 +232,12 @@ public final class DBTasks { }.start(); } + /** + * Requests the download of a list of FeedItem objects. + * + * @param context Used for requesting the download and accessing the DB. + * @param items The FeedItem objects. + */ public static void downloadFeedItems(final Context context, FeedItem... items) throws DownloadRequestException { downloadFeedItems(true, context, items); @@ -245,6 +299,14 @@ public final class DBTasks { return counter; } + /** + * Looks for undownloaded episodes in the queue or list of unread items and request a download if + * 1. Network is available + * 2. There is free space in the episode cache + * This method should NOT be executed on the GUI thread. + * + * @param context Used for accessing the DB. + */ public static void autodownloadUndownloadedItems(final Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Performing auto-dl of undownloaded episodes"); @@ -330,6 +392,14 @@ public final class DBTasks { return 0; } + /** + * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller + * 'playbackCompletionDate'-value will be deleted first. + *

+ * This method should NOT be executed on the GUI thread. + * + * @param context Used for accessing the DB. + */ public static void performAutoCleanup(final Context context) { performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); } @@ -383,11 +453,23 @@ public final class DBTasks { return counter; } + /** + * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread. + */ public static void enqueueAllNewItems(final Context context) { long[] unreadItems = DBReader.getUnreadItemIds(context); DBWriter.addQueueItem(context, unreadItems); } + /** + * Returns the successor of a FeedItem in the queue. + * + * @param context Used for accessing the DB. + * @param itemId ID of the FeedItem + * @param queue Used for determining the successor of the item. If this parameter is null, the method will load + * the queue from the database in the same thread. + * @return Successor of the FeedItem or null if the FeedItem is not in the queue or has no successor. + */ public static FeedItem getQueueSuccessorOfItem(Context context, final long itemId, List queue) { FeedItem result = null; @@ -409,6 +491,13 @@ public final class DBTasks { return result; } + /** + * Loads the queue from the database and checks if the specified FeedItem is in the queue. + * This method should NOT be executed in the GUI thread. + * + * @param context Used for accessing the DB. + * @param feedItemId ID of the FeedItem + */ public static boolean isInQueue(Context context, final long feedItemId) { List queue = DBReader.getQueueIDList(context); return QueueAccess.IDListAccess(queue).contains(feedItemId); @@ -438,6 +527,16 @@ public final class DBTasks { return null; } + /** + * Adds a new Feed to the database or updates the old version if it already exists. If another Feed with the same + * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed. + * These FeedItems will be marked as unread. + * This method should NOT be executed on the GUI thread. + * + * @param context Used for accessing the DB. + * @param newFeed The new Feed object. + * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise. + */ public static synchronized Feed updateFeed(final Context context, final Feed newFeed) { // Look up feed in the feedslist @@ -505,11 +604,13 @@ public final class DBTasks { } /** - * Searches the titles of FeedItems of a specific feed for a given + * Searches the titles of FeedItems of a specific Feed for a given * string. * + * @param context Used for accessing the DB. * @param feedID The id of the feed whose items should be searched. - * @param query The search string + * @param query The search string. + * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. */ public static FutureTask> searchFeedItemTitle(final Context context, final long feedID, final String query) { @@ -527,11 +628,13 @@ public final class DBTasks { } /** - * Searches the descriptions of FeedItems of a specific feed for a given + * Searches the descriptions of FeedItems of a specific Feed for a given * string. * - * @param feedID The id of the feed whose items should be searched. - * @param query The search string + * @param context Used for accessing the DB. + * @param feedID The id of the feed whose items should be searched. + * @param query The search string + * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. */ public static FutureTask> searchFeedItemDescription(final Context context, final long feedID, final String query) { @@ -549,11 +652,13 @@ public final class DBTasks { } /** - * Searches the 'contentEncoded' field of FeedItems of a specific feed for a - * given string. + * Searches the contentEncoded-value of FeedItems of a specific Feed for a given + * string. * + * @param context Used for accessing the DB. * @param feedID The id of the feed whose items should be searched. - * @param query The search string + * @param query The search string + * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. */ public static FutureTask> searchFeedItemContentEncoded(final Context context, final long feedID, final String query) { @@ -571,10 +676,12 @@ public final class DBTasks { } /** - * Searches chapters for a given string. + * Searches chapters of the FeedItems of a specific Feed for a given string. * + * @param context Used for accessing the DB. * @param feedID The id of the feed whose items should be searched. - * @param query The search string + * @param query The search string + * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. */ public static FutureTask> searchFeedItemChapters(final Context context, final long feedID, final String query) { -- cgit v1.2.3