diff options
Diffstat (limited to 'src/de/danoeh/antennapod/storage')
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBReader.java | 908 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBTasks.java | 895 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBWriter.java | 974 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DownloadRequestException.java | 25 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DownloadRequester.java | 367 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/FeedItemStatistics.java | 70 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/FeedSearcher.java | 57 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/PodDBAdapter.java | 1391 |
8 files changed, 0 insertions, 4687 deletions
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java deleted file mode 100644 index e49ea4f83..000000000 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ /dev/null @@ -1,908 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.service.download.DownloadStatus; -import de.danoeh.antennapod.util.DownloadError; -import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; -import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator; -import de.danoeh.antennapod.util.flattr.FlattrStatus; -import de.danoeh.antennapod.util.flattr.FlattrThing; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -/** - * 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"; - - /** - * 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<Feed> getFeedList(final Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feedlist"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<Feed> result = getFeedList(adapter); - adapter.close(); - return result; - } - - private static List<Feed> getFeedList(PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feedlist"); - - Cursor feedlistCursor = adapter.getAllFeedsCursor(); - List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - /** - * Returns a list with the download URLs of all feeds. - * - * @param context A context that is used for opening the database connection. - * @return A list of Strings with the download URLs of all feeds. - */ - public static List<String> getFeedListDownloadUrls(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - List<String> result = new ArrayList<String>(); - adapter.open(); - Cursor feeds = adapter.getFeedCursorDownloadUrls(); - if (feeds.moveToFirst()) { - do { - result.add(feeds.getString(1)); - } while (feeds.moveToNext()); - } - feeds.close(); - adapter.close(); - - return result; - } - - /** - * 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)}. - */ - public static List<Feed> getExpiredFeedsList(final Context context, final long expirationTime) { - if (BuildConfig.DEBUG) - Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); - List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - /** - * Takes a list of FeedItems and loads their corresponding Feed-objects from the database. - * The feedID-attribute of a FeedItem must be set to the ID of its feed or the method will - * not find the correct feed of an item. - * - * @param 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<FeedItem> items) { - List<Feed> 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()); - } - } - } - - /** - * 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<FeedItem> getFeedItemList(Context context, - final Feed feed) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed); - List<FeedItem> items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - Collections.sort(items, new FeedItemPubdateComparator()); - - adapter.close(); - - for (FeedItem item : items) { - item.setFeed(feed); - } - - return items; - } - - static List<FeedItem> extractItemlistFromCursor(Context context, Cursor itemlistCursor) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor); - adapter.close(); - return result; - } - - private static List<FeedItem> extractItemlistFromCursor( - PodDBAdapter adapter, Cursor itemlistCursor) { - ArrayList<String> itemIds = new ArrayList<String>(); - List<FeedItem> items = new ArrayList<FeedItem>( - 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)); - item.setFlattrStatus(new FlattrStatus(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS))); - - long imageIndex = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_IMAGE); - if (imageIndex != 0) { - item.setImage(getFeedImage(adapter, imageIndex)); - } - - // 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<Chapter>()); - 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; - } - if (chapter != null) { - 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<FeedItem> items, ArrayList<String> itemIds) { - - List<FeedItem> itemsCopy = new ArrayList<FeedItem>(items); - Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds - .toArray(new String[itemIds.size()])); - 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)); - 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, - cursor.getInt(PodDBAdapter.KEY_PLAYED_DURATION_INDEX)); - } - - private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, - Cursor cursor) { - Date lastUpdate = new Date( - cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_LASTUPDATE)); - - final FeedImage image; - long imageIndex = cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_IMAGE); - if (imageIndex != 0) { - image = getFeedImage(adapter, imageIndex); - } else { - image = null; - } - Feed feed = new Feed(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_ID), - lastUpdate, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_TITLE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_LINK), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DESCRIPTION), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_PAYMENT_LINK), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_AUTHOR), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_LANGUAGE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_TYPE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FEED_IDENTIFIER), - image, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FILE_URL), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOAD_URL), - cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0, - new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS))); - - if (image != null) { - image.setOwner(feed); - } - - FeedPreferences preferences = new FeedPreferences(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_ID), - cursor.getInt(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD) > 0, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_USERNAME), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_PASSWORD)); - - feed.setPreferences(preferences); - return feed; - } - - private static FeedItem getMatchingItemForMedia(long itemId, - List<FeedItem> items) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return item; - } - } - return null; - } - - static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - Cursor itemlistCursor = adapter.getQueueCursor(); - List<FeedItem> items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - - 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<Long> getQueueIDList(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - - adapter.open(); - List<Long> result = getQueueIDList(adapter); - adapter.close(); - - return result; - } - - static List<Long> getQueueIDList(PodDBAdapter adapter) { - adapter.open(); - Cursor queueCursor = adapter.getQueueIDCursor(); - - List<Long> queueIds = new ArrayList<Long>(queueCursor.getCount()); - if (queueCursor.moveToFirst()) { - do { - queueIds.add(queueCursor.getLong(0)); - } while (queueCursor.moveToNext()); - } - 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<FeedItem> getQueue(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<FeedItem> items = getQueue(context, adapter); - adapter.close(); - 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<FeedItem> getDownloadedItems(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting downloaded items"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getDownloadedItemsCursor(); - List<FeedItem> items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - Collections.sort(items, new FeedItemPubdateComparator()); - - adapter.close(); - return items; - - } - - /** - * 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<FeedItem> getUnreadItemsList(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting unread items list"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getUnreadItemsCursor(); - List<FeedItem> items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - loadFeedDataOfFeedItemlist(context, items); - - adapter.close(); - - return items; - } - - /** - * Loads the IDs of the FeedItems whose 'read'-attribute is set to false. - * - * @param context A context that is used for opening a database connection. - * @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(); - 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; - } - - - /** - * Loads a list of FeedItems sorted by pubDate in descending order. - * - * @param context A context that is used for opening a database connection. - * @param limit The maximum number of episodes that should be loaded. - */ - public static List<FeedItem> getRecentlyPublishedEpisodes(Context context, int limit) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting recently published items list"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getRecentlyPublishedItemsCursor(limit); - List<FeedItem> items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - loadFeedDataOfFeedItemlist(context, items); - - adapter.close(); - - return items; - } - - /** - * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode - * has been completed at least once. - * - * @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<FeedItem> getPlaybackHistory(final Context context) { - if (BuildConfig.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<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor); - loadFeedDataOfFeedItemlist(context, items); - itemCursor.close(); - adapter.close(); - - Collections.sort(items, new PlaybackCompletionDateComparator()); - 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<DownloadStatus> getDownloadLog(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting DownloadLog"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE); - List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>( - 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, DownloadError.fromCode(reason), completionDate, - reasonDetailed)); - - } while (logCursor.moveToNext()); - } - logCursor.close(); - Collections.sort(downloadLog, new DownloadStatusComparator()); - return downloadLog; - } - - /** - * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over - * {@link #getFeedItemList(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<FeedItemStatistics> getFeedStatisticsList(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<FeedItemStatistics> result = new ArrayList<FeedItemStatistics>(); - 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; - } - - /** - * 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) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Feed result = getFeed(context, feedId, adapter); - adapter.close(); - return result; - } - - static Feed getFeed(final Context context, final long feedId, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feed with id " + feedId); - Feed feed = null; - - 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); - } - feedCursor.close(); - return feed; - } - - static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feeditem with id " + itemId); - FeedItem item = null; - - Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId)); - if (itemCursor.moveToFirst()) { - List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor); - if (list.size() > 0) { - item = list.get(0); - loadFeedDataOfFeedItemlist(context, list); - } - } - return item; - - } - - /** - * 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 (BuildConfig.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; - - } - - /** - * 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(); - 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(); - } - - /** - * 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(); - final int result = adapter.getNumberOfDownloadedEpisodes(); - adapter.close(); - 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(); - final int result = adapter.getNumberOfUnreadItems(); - adapter.close(); - return result; - } - - /** - * 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 - */ - 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. - * - * @param id The id of the object - * @return The found object - */ - static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { - Cursor cursor = adapter.getImageCursor(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 context A context that is used for opening a database connection. - * @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; - } - - /** - * Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems. - * - * @param context A context that is used for opening a database connection. - * @return The flattr queue as a List. - */ - public static List<FlattrThing> getFlattrQueue(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<FlattrThing> result = new ArrayList<FlattrThing>(); - - // load feeds - Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor(); - if (feedCursor.moveToFirst()) { - do { - result.add(extractFeedFromCursorRow(adapter, feedCursor)); - } while (feedCursor.moveToNext()); - } - feedCursor.close(); - - //load feed items - Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor(); - result.addAll(extractItemlistFromCursor(adapter, feedItemCursor)); - feedItemCursor.close(); - - adapter.close(); - Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items."); - return result; - } - - - /** - * Returns true if the flattr queue is empty. - * - * @param context A context that is used for opening a database connection. - */ - public static boolean getFlattrQueueEmpty(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - boolean empty = adapter.getFlattrQueueSize() == 0; - adapter.close(); - return empty; - } - - /** - * Returns data necessary for displaying the navigation drawer. This includes - * the list of subscriptions, the number of items in the queue and the number of unread - * items. - * - * @param context A context that is used for opening a database connection. - */ - public static NavDrawerData getNavDrawerData(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List<Feed> feeds = getFeedList(adapter); - int queueSize = adapter.getQueueSize(); - int numUnreadItems = adapter.getNumberOfUnreadItems(); - NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems); - adapter.close(); - return result; - } - - public static class NavDrawerData { - public List<Feed> feeds; - public int queueSize; - public int numUnreadItems; - - public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) { - this.feeds = feeds; - this.queueSize = queueSize; - this.numUnreadItems = numUnreadItems; - } - } -} diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java deleted file mode 100644 index a230ba797..000000000 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ /dev/null @@ -1,895 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.asynctask.FlattrClickWorker; -import de.danoeh.antennapod.asynctask.FlattrStatusFetcher; -import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.service.GpodnetSyncService; -import de.danoeh.antennapod.service.download.DownloadStatus; -import de.danoeh.antennapod.service.playback.PlaybackService; -import de.danoeh.antennapod.util.DownloadError; -import de.danoeh.antennapod.util.NetworkUtils; -import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; -import de.danoeh.antennapod.util.flattr.FlattrUtils; - -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Provides methods for doing common tasks that use DBReader and DBWriter. - */ -public final class DBTasks { - private static final String TAG = "DBTasks"; - - /** - * Executor service used by the autodownloadUndownloadedEpisodes method. - */ - private static ExecutorService autodownloadExec; - - static { - autodownloadExec = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - } - - private DBTasks() { - } - - /** - * Removes the feed with the given download url. This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the db - * @param downloadUrl URL of the feed. - */ - public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor cursor = adapter.getFeedCursorDownloadUrls(); - long feedID = 0; - if (cursor.moveToFirst()) { - do { - if (cursor.getString(1).equals(downloadUrl)) { - feedID = cursor.getLong(0); - } - } while (cursor.moveToNext()); - } - cursor.close(); - adapter.close(); - - if (feedID != 0) { - try { - DBWriter.deleteFeed(context, feedID).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } else { - Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl); - } - } - - /** - * 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 { - 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 media player - 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 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<Feed> feeds) { - if (isRefreshing.compareAndSet(false, true)) { - new Thread() { - public void run() { - if (feeds != null) { - refreshFeeds(context, feeds); - } else { - refreshFeeds(context, DBReader.getFeedList(context)); - } - isRefreshing.set(false); - - if (FlattrUtils.hasToken()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things."); - new FlattrClickWorker(context).executeAsync(); // flattr pending things - - if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status."); - new FlattrStatusFetcher(context).start(); - - } - GpodnetSyncService.sendSyncIntent(context); - autodownloadUndownloadedItems(context); - } - }.start(); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Ignoring request to refresh all feeds: Refresh lock is locked"); - } - } - - /** - * Used by refreshExpiredFeeds to determine which feeds should be refreshed. - * This method will use the value specified in the UserPreferences as the - * expiration time. - * - * @param context Used for DB access. - * @return A list of expired feeds. An empty list will be returned if there - * are no expired feeds. - */ - public static List<Feed> getExpiredFeeds(final Context context) { - long millis = UserPreferences.getUpdateInterval(); - - if (millis > 0) { - - List<Feed> feedList = DBReader.getExpiredFeedsList(context, - millis); - if (feedList.size() > 0) { - refreshFeeds(context, feedList); - } - return feedList; - } else { - return new ArrayList<Feed>(); - } - } - - /** - * 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 (BuildConfig.DEBUG) - Log.d(TAG, "Refreshing expired feeds"); - - new Thread() { - public void run() { - refreshFeeds(context, getExpiredFeeds(context)); - } - }.start(); - } - - private static void refreshFeeds(final Context context, - final List<Feed> 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. - * - * @param context Used for requesting the download. - * @param feed The Feed object. - */ - public static void refreshFeed(Context context, Feed feed) - throws DownloadRequestException { - Feed f; - if (feed.getPreferences() == null) { - f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle()); - } else { - f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle(), - feed.getPreferences().getUsername(), feed.getPreferences().getPassword()); - } - f.setId(feed.getId()); - DownloadRequester.getInstance().downloadFeed(context, f); - } - - /** - * 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 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) { - e.printStackTrace(); - Log.w(TAG, "Failed to download invalid feed image"); - } - } - - /** - * 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, - "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(); - } - - /** - * 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() { - List<FeedItem> queue = DBReader.getQueue(context); - if (!queue.isEmpty()) { - try { - downloadFeedItems(context, - queue.toArray(new FeedItem[queue.size()])); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } - } - }.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); - } - - 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<FeedItem> queue, final List<FeedItem> unreadItems) { - int counter = 0; - for (FeedItem item : queue) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - for (FeedItem item : unreadItems) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - 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 is executed on an internal single thread executor. - * - * @param context Used for accessing the DB. - * @param mediaIds If this list is not empty, the method will only download a candidate for automatic downloading if - * its media ID is in the mediaIds list. - * @return A Future that can be used for waiting for the methods completion. - */ - public static Future<?> autodownloadUndownloadedItems(final Context context, final long... mediaIds) { - return autodownloadExec.submit(new Runnable() { - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Performing auto-dl of undownloaded episodes"); - if (NetworkUtils.autodownloadNetworkAvailable(context) - && UserPreferences.isEnableAutodownload()) { - final List<FeedItem> queue = DBReader.getQueue(context); - final List<FeedItem> 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); - } - - Arrays.sort(mediaIds); // sort for binary search - final boolean ignoreMediaIds = mediaIds.length == 0; - List<FeedItem> itemsToDownload = new ArrayList<FeedItem>(); - - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (int i = 0; i < queue.size(); i++) { // ignore playing item - FeedItem item = queue.get(i); - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (FeedItem item : unreadItems) { - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - if (BuildConfig.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; - } - - /** - * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller - * 'playbackCompletionDate'-value will be deleted first. - * <p/> - * 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)); - } - - private static int performAutoCleanup(final Context context, - final int episodeNumber) { - List<FeedItem> candidates = new ArrayList<FeedItem>(); - List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context); - QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context)); - List<FeedItem> delete; - for (FeedItem item : downloadedItems) { - if (item.hasMedia() && item.getMedia().isDownloaded() - && !queue.contains(item.getId()) && item.isRead()) { - candidates.add(item); - } - - } - - Collections.sort(candidates, new Comparator<FeedItem>() { - @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) { - try { - DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - int counter = delete.size(); - - if (BuildConfig.DEBUG) - Log.d(TAG, String.format( - "Auto-delete deleted %d episodes (%d requested)", counter, - episodeNumber)); - - 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<FeedItem> queue) { - FeedItem result = null; - if (queue == null) { - queue = DBReader.getQueue(context); - } - if (queue != null) { - Iterator<FeedItem> iterator = queue.iterator(); - while (iterator.hasNext()) { - FeedItem item = iterator.next(); - if (item.getId() == itemId) { - if (iterator.hasNext()) { - result = iterator.next(); - } - break; - } - } - } - 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<Long> queue = DBReader.getQueueIDList(context); - return QueueAccess.IDListAccess(queue).contains(feedItemId); - } - - private static Feed searchFeedByIdentifyingValueOrID(Context context, PodDBAdapter adapter, - Feed feed) { - if (feed.getId() != 0) { - return DBReader.getFeed(context, feed.getId(), adapter); - } else { - List<Feed> feeds = DBReader.getFeedList(context); - for (Feed f : feeds) { - if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { - f.setItems(DBReader.getFeedItemList(context, f)); - return f; - } - } - } - 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; - } - - /** - * Adds new Feeds to the database or updates the old versions if they 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. - * <p/> - * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior. - * <p/> - * This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the DB. - * @param newFeeds The new Feed objects. - * @return The updated Feeds 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... newFeeds) { - List<Feed> newFeedsList = new ArrayList<Feed>(); - List<Feed> updatedFeedsList = new ArrayList<Feed>(); - Feed[] resultFeeds = new Feed[newFeeds.length]; - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - for (int feedIdx = 0; feedIdx < newFeeds.length; feedIdx++) { - - final Feed newFeed = newFeeds[feedIdx]; - - // Look up feed in the feedslist - final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter, - newFeed); - if (savedFeed == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Found no existing Feed with title " - + newFeed.getTitle() + ". Adding as new one." - ); - // Add a new Feed - newFeedsList.add(newFeed); - resultFeeds[feedIdx] = newFeed; - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed with title " + newFeed.getTitle() - + " already exists. Syncing new with existing one."); - - Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); - if (savedFeed.compareWithOther(newFeed)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Feed has updated attribute values. Updating old feed's attributes"); - savedFeed.updateFromOther(newFeed); - } - if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); - savedFeed.getPreferences().updateFromOther(newFeed.getPreferences()); - } - // 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); - item.setRead(false); - } else { - oldItem.updateFromOther(item); - } - } - // update attributes - savedFeed.setLastUpdate(newFeed.getLastUpdate()); - savedFeed.setType(newFeed.getType()); - - updatedFeedsList.add(savedFeed); - resultFeeds[feedIdx] = savedFeed; - } - } - - adapter.close(); - - try { - DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get(); - DBWriter.setCompleteFeed(context, updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - - return resultFeeds; - } - - /** - * 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. - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context, - final long feedID, final String query) { - return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemTitles(feedID, - query); - List<FeedItem> 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 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<List<FeedItem>> searchFeedItemDescription(final Context context, - final long feedID, final String query) { - return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemDescriptions(feedID, - query); - List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * 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 - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context, - final long feedID, final String query) { - return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemContentEncoded(feedID, - query); - List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * 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 - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context, - final long feedID, final String query) { - return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemChapters(feedID, - query); - List<FeedItem> 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<T> implements Callable<T> { - 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; - } - } - - /** - * Adds the given FeedItem to the flattr queue if the user is logged in. Otherwise, a dialog - * will be opened that lets the user go either to the login screen or the website of the flattr thing. - * - * @param context - * @param item - */ - public static void flattrItemIfLoggedIn(Context context, FeedItem item) { - if (FlattrUtils.hasToken()) { - item.getFlattrStatus().setFlattrQueue(); - DBWriter.setFlattredStatus(context, item, true); - } else { - FlattrUtils.showNoTokenDialogOrRedirect(context, item.getPaymentLink()); - } - } - - /** - * Adds the given Feed to the flattr queue if the user is logged in. Otherwise, a dialog - * will be opened that lets the user go either to the login screen or the website of the flattr thing. - * - * @param context - * @param feed - */ - public static void flattrFeedIfLoggedIn(Context context, Feed feed) { - if (FlattrUtils.hasToken()) { - feed.getFlattrStatus().setFlattrQueue(); - DBWriter.setFlattredStatus(context, feed, true); - } else { - FlattrUtils.showNoTokenDialogOrRedirect(context, feed.getPaymentLink()); - } - } - -} diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java deleted file mode 100644 index 9916ac97f..000000000 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ /dev/null @@ -1,974 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.app.backup.BackupManager; -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.BuildConfig; -import de.danoeh.antennapod.asynctask.FlattrClickWorker; -import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.preferences.GpodnetPreferences; -import de.danoeh.antennapod.preferences.PlaybackPreferences; -import de.danoeh.antennapod.service.download.DownloadStatus; -import de.danoeh.antennapod.service.playback.PlaybackService; -import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.flattr.FlattrStatus; -import de.danoeh.antennapod.util.flattr.FlattrThing; -import de.danoeh.antennapod.util.flattr.SimpleFlattrThing; -import org.shredzone.flattr4j.model.Flattr; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; - -/** - * 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() { - } - - /** - * 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) { - Log.i(TAG, String.format("Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s", - media.getId(), media.getEpisodeTitle(), String.valueOf(media.isDownloaded()))); - 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); - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setMedia(media); - adapter.close(); - - // 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 (BuildConfig.DEBUG) - Log.d(TAG, "Deleting File. Result: " + result); - EventDistributor.getInstance().sendQueueUpdateBroadcast(); - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - } - }); - } - - /** - * 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(); - } - - // 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<FeedItem> 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 (item.hasItemImage()) { - FeedImage image = item.getImage(); - if (image.isDownloaded() && image.getFile_url() != null) { - File imgFile = new File(image.getFile_url()); - imgFile.delete(); - } else if (requester.isDownloadingFile(image)) { - requester.cancelDownload(context, item.getImage()); - } - } - } - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - if (queueWasModified) { - adapter.setQueue(queue); - } - adapter.removeFeed(feed); - adapter.close(); - - GpodnetPreferences.addRemovedFeed(feed.getDownload_url()); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - - BackupManager backupManager = new BackupManager(context); - backupManager.dataChanged(); - } - } - }); - } - - /** - * 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 (BuildConfig.DEBUG) - Log.d(TAG, "Adding new item to playback history"); - media.setPlaybackCompletionDate(new Date()); - // reset played_duration to 0 so that it behaves correctly when the episode is played again - media.setPlayedDuration(0); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedMediaPlaybackCompletionDate(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 (BuildConfig.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); - 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<FeedItem> 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) { - DBTasks.autodownloadUndownloadedItems(context); - } - - } - }); - - } - - /** - * 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<FeedItem> queue = DBReader.getQueue(context, - adapter); - - if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModified = false; - List<FeedItem> itemsToSave = new LinkedList<FeedItem>(); - 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(); - DBTasks.autodownloadUndownloadedItems(context); - } - } - }); - - } - - /** - * 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<FeedItem> 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 { - Log.w(TAG, "Queue was not modified by call to removeQueueItem"); - } - } else { - Log.e(TAG, "removeQueueItem: Could not load queue"); - } - adapter.close(); - if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); - } - } - }); - - } - - /** - * Moves the specified item to the top of the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemId The item to move to the top of the queue - * @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. - */ - public static Future<?> moveQueueItemToTop(final Context context, final long itemId, final boolean broadcastUpdate) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - List<Long> queueIdList = DBReader.getQueueIDList(context); - int currentLocation = 0; - for (long id : queueIdList) { - if (id == itemId) { - moveQueueItemHelper(context, currentLocation, 0, broadcastUpdate); - return; - } - currentLocation++; - } - Log.e(TAG, "moveQueueItemToTop: item not found"); - } - }); - } - - /** - * Moves the specified item to the bottom of the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemId The item to move to the bottom of the queue - * @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. - */ - public static Future<?> moveQueueItemToBottom(final Context context, final long itemId, - final boolean broadcastUpdate) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - List<Long> queueIdList = DBReader.getQueueIDList(context); - int currentLocation = 0; - for (long id : queueIdList) { - if (id == itemId) { - moveQueueItemHelper(context, currentLocation, queueIdList.size() - 1, - broadcastUpdate); - return; - } - currentLocation++; - } - Log.e(TAG, "moveQueueItemToBottom: item not found"); - } - }); - } - - /** - * 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() { - moveQueueItemHelper(context, from, to, broadcastUpdate); - } - }); - } - - /** - * Changes the position of a FeedItem in the queue. - * <p/> - * This function must be run using the ExecutorService (dbExec). - * - * @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()) - */ - private static void moveQueueItemHelper(final Context context, final int from, - final int to, final boolean broadcastUpdate) { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List<FeedItem> 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(); - } - - } - } else { - Log.e(TAG, "moveQueueItemHelper: Could not load queue"); - } - adapter.close(); - } - - /** - * 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; - 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); - } - - 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.moveToNext(); - } - 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.moveToNext(); - } - itemCursor.close(); - adapter.setFeedItemRead(true, itemIds); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - - } - - static Future<?> addNewFeed(final Context context, final Feed... feeds) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feeds); - adapter.close(); - - for (Feed feed : feeds) { - GpodnetPreferences.addAddedFeed(feed.getDownload_url()); - } - - BackupManager backupManager = new BackupManager(context); - backupManager.dataChanged(); - } - }); - } - - static Future<?> setCompleteFeed(final Context context, final Feed... feeds) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feeds); - adapter.close(); - - } - }); - - } - - /** - * 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 the 'position' and 'duration' attributes of a FeedMedia object - * - * @param context A context that is used for opening a database connection. - * @param media The FeedMedia object. - */ - public static Future<?> setFeedMediaPlaybackInformation(final Context context, final FeedMedia media) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedMediaPlaybackInformation(media); - adapter.close(); - } - }); - } - - /** - * 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) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - 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) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setImage(image); - adapter.close(); - } - }); - } - - /** - * Updates download URLs of feeds from a given Map. The key of the Map is the original URL of the feed - * and the value is the updated URL - */ - public static Future<?> updateFeedDownloadURLs(final Context context, final Map<String, String> urls) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - for (String key : urls.keySet()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing URL " + key + " with url " + urls.get(key)); - - adapter.setFeedDownloadUrl(key, urls.get(key)); - } - adapter.close(); - } - }); - } - - /** - * Saves a FeedPreferences object in the database. The Feed ID of the FeedPreferences-object MUST NOT be 0. - * - * @param context Used for opening a database connection. - * @param preferences The FeedPreferences object. - */ - public static Future<?> setFeedPreferences(final Context context, final FeedPreferences preferences) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedPreferences(preferences); - adapter.close(); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - }); - } - - private static boolean itemListContains(List<FeedItem> items, long itemId) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return true; - } - } - return false; - } - - /** - * Saves the FlattrStatus of a FeedItem object in the database. - * - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - */ - public static Future<?> setFeedItemFlattrStatus(final Context context, - final FeedItem item, - final boolean startFlattrClickWorker) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedItemFlattrStatus(item); - adapter.close(); - if (startFlattrClickWorker) { - new FlattrClickWorker(context).executeAsync(); - } - } - }); - } - - /** - * Saves the FlattrStatus of a Feed object in the database. - * - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - */ - private static Future<?> setFeedFlattrStatus(final Context context, - final Feed feed, - final boolean startFlattrClickWorker) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedFlattrStatus(feed); - adapter.close(); - if (startFlattrClickWorker) { - new FlattrClickWorker(context).executeAsync(); - } - } - }); - } - - /** - * format an url for querying the database - * (postfix a / and apply percent-encoding) - */ - private static String formatURIForQuery(String uri) { - try { - return URLEncoder.encode(uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri, "UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, e.getMessage()); - return ""; - } - } - - - /** - * Set flattr status of the passed thing (either a FeedItem or a Feed) - * - * @param context - * @param thing - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - * @return - */ - public static Future<?> setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) { - // must propagate this to back db - if (thing instanceof FeedItem) - return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker); - else if (thing instanceof Feed) - return setFeedFlattrStatus(context, (Feed) thing, startFlattrClickWorker); - else if (thing instanceof SimpleFlattrThing) { - } // SimpleFlattrThings are generated on the fly and do not have DB backing - else - Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed nor SimpleFlattrThing"); - - return null; - } - - /** - * Reset flattr status to unflattrd for all items - */ - public static Future<?> clearAllFlattrStatus(final Context context) { - Log.d(TAG, "clearAllFlattrStatus()"); - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearAllFlattrStatus(); - adapter.close(); - } - }); - } - - /** - * Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp, - * where the information has been retrieved from the flattr API - */ - public static Future<?> setFlattredStatus(final Context context, final List<Flattr> flattrList) { - Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items"); - // clear flattr status in db - clearAllFlattrStatus(context); - - // submit list with flattred things having normalized URLs to db - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - for (Flattr flattr : flattrList) { - adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime())); - } - adapter.close(); - } - }); - } -} diff --git a/src/de/danoeh/antennapod/storage/DownloadRequestException.java b/src/de/danoeh/antennapod/storage/DownloadRequestException.java deleted file mode 100644 index 0ef766e58..000000000 --- a/src/de/danoeh/antennapod/storage/DownloadRequestException.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.storage; - -/** - * Thrown by the DownloadRequester if a download request contains invalid data - * or something went wrong while processing the request. - */ -public class DownloadRequestException extends Exception { - - public DownloadRequestException() { - super(); - } - - public DownloadRequestException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public DownloadRequestException(String detailMessage) { - super(detailMessage); - } - - public DownloadRequestException(Throwable throwable) { - super(throwable); - } - -} diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java deleted file mode 100644 index d305c572b..000000000 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ /dev/null @@ -1,367 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.webkit.URLUtil; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.feed.*; -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; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringEscapeUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; - -import java.io.File; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - - -/** - * Sends download requests to the DownloadService. This class should always be used for starting downloads, - * otherwise they won't work correctly. - */ -public class DownloadRequester { - private static final String TAG = "DownloadRequester"; - - public static final String IMAGE_DOWNLOADPATH = "images/"; - public static final String FEED_DOWNLOADPATH = "cache/"; - public static final String MEDIA_DOWNLOADPATH = "media/"; - - private static DownloadRequester downloader; - - Map<String, DownloadRequest> downloads; - - private DownloadRequester() { - downloads = new ConcurrentHashMap<String, DownloadRequest>(); - } - - public static synchronized DownloadRequester getInstance() { - if (downloader == null) { - downloader = new DownloadRequester(); - } - return downloader; - } - - /** - * Starts a new download with the given DownloadRequest. This method should only - * be used from outside classes if the DownloadRequest was created by the DownloadService to - * ensure that the data is valid. Use downloadFeed(), downloadImage() or downloadMedia() instead. - * - * @param context Context object for starting the DownloadService - * @param request The DownloadRequest. If another DownloadRequest with the same source URL is already stored, this method - * call will return false. - * @return True if the download request was accepted, false otherwise. - */ - public boolean download(Context context, DownloadRequest request) { - Validate.notNull(context); - Validate.notNull(request); - - if (downloads.containsKey(request.getSource())) { - if (BuildConfig.DEBUG) Log.i(TAG, "DownloadRequest is already stored."); - return false; - } - downloads.put(request.getSource(), request); - - Intent launchIntent = new Intent(context, DownloadService.class); - launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); - context.startService(launchIntent); - EventDistributor.getInstance().sendDownloadQueuedBroadcast(); - return true; - } - - private void download(Context context, FeedFile item, File dest, - boolean overwriteIfExists, String username, String password, boolean deleteOnFailure) { - if (!isDownloadingFile(item)) { - if (!isFilenameAvailable(dest.toString()) || (deleteOnFailure && dest.exists())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Filename already used."); - if (isFilenameAvailable(dest.toString()) && overwriteIfExists) { - boolean result = dest.delete(); - if (BuildConfig.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.EXTENSION_SEPARATOR - + FilenameUtils.getExtension(dest.getName()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Testing filename " + newName); - newDest = new File(dest.getParent(), newName); - if (!newDest.exists() - && isFilenameAvailable(newDest.toString())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "File doesn't exist yet. Using " - + newName); - break; - } - } - if (newDest != null) { - dest = newDest; - } - } - } - if (BuildConfig.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(), - URLChecker.prepareURL(item.getDownload_url()), item.getHumanReadableIdentifier(), - item.getId(), item.getTypeAsInt(), username, password, deleteOnFailure); - - download(context, request); - } 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 (BuildConfig.DEBUG) - Log.d(TAG, path - + " is already used by another requested download"); - return false; - } - } - if (BuildConfig.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)) { - String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null; - String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null; - - download(context, feed, new File(getFeedfilePath(context), - getFeedfileName(feed)), true, username, password, true); - } - } - - public void downloadImage(Context context, FeedImage image) - throws DownloadRequestException { - if (feedFileValid(image)) { - download(context, image, new File(getImagefilePath(context), - getImagefileName(image)), false, null, null, false); - } - } - - public void downloadMedia(Context context, FeedMedia feedmedia) - throws DownloadRequestException { - if (feedFileValid(feedmedia)) { - Feed feed = feedmedia.getItem().getFeed(); - String username; - String password; - if (feed != null && feed.getPreferences() != null) { - username = feed.getPreferences().getUsername(); - password = feed.getPreferences().getPassword(); - } else { - username = null; - password = null; - } - - File dest; - if (feedmedia.getFile_url() != null) { - dest = new File(feedmedia.getFile_url()); - } else { - dest = new File(getMediafilePath(context, feedmedia), - getMediafilename(feedmedia)); - } - download(context, feedmedia, - dest, false, username, password, 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 (BuildConfig.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 (BuildConfig.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.getOwner() != null && image.getOwner().getHumanReadableIdentifier() != null) { - filename = image.getOwner().getHumanReadableIdentifier(); - } - 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) { - String filename; - String titleBaseFilename = ""; - - // Try to generate the filename by the item title - if (media.getItem() != null && media.getItem().getTitle() != null) { - String title = media.getItem().getTitle(); - // Delete reserved characters - titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", ""); - titleBaseFilename = titleBaseFilename.trim(); - } - - String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(), - null, media.getMime_type()); - ; - - if (titleBaseFilename != "") { - // Append extension - filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR + - FilenameUtils.getExtension(URLBaseFilename); - } else { - // Fall back on URL file name - filename = URLBaseFilename; - } - return filename; - } -} diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java deleted file mode 100644 index 8cb040756..000000000 --- a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java +++ /dev/null @@ -1,70 +0,0 @@ -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; - private static final Date UNKNOWN_DATE = new Date(0); - - - /** - * Creates new FeedItemStatistics object. - * - * @param feedID ID of the feed. - * @param numberOfItems Number of items that this feed has. - * @param numberOfNewItems Number of unread items this feed has. - * @param numberOfInProgressItems Number of items that the user has started listening to. - * @param lastUpdate pubDate of the latest episode. A lastUpdate value of 0 will be interpreted as DATE_UNKOWN if - * numberOfItems is 0. - */ - public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) { - this.feedID = feedID; - this.numberOfItems = numberOfItems; - this.numberOfNewItems = numberOfNewItems; - this.numberOfInProgressItems = numberOfInProgressItems; - if (numberOfItems > 0) { - this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } else { - this.lastUpdate = UNKNOWN_DATE; - } - } - - public long getFeedID() { - return feedID; - } - - public int getNumberOfItems() { - return numberOfItems; - } - - public int getNumberOfNewItems() { - return numberOfNewItems; - } - - public int getNumberOfInProgressItems() { - return numberOfInProgressItems; - } - - /** - * Returns the pubDate of the latest item in the feed. Users of this method - * should check if this value is unkown or not by calling lastUpdateKnown() first. - */ - public Date getLastUpdate() { - return (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } - - /** - * Returns true if the lastUpdate value is known. The lastUpdate value is unkown if the - * feed has no items. - */ - public boolean lastUpdateKnown() { - return lastUpdate != UNKNOWN_DATE; - } -} diff --git a/src/de/danoeh/antennapod/storage/FeedSearcher.java b/src/de/danoeh/antennapod/storage/FeedSearcher.java deleted file mode 100644 index e7aa93f83..000000000 --- a/src/de/danoeh/antennapod/storage/FeedSearcher.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.content.Context; -import de.danoeh.antennapod.R; -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<SearchResult> 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<SearchResult> result = new ArrayList<SearchResult>(); - - FutureTask<List<FeedItem>>[] 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<FeedItem> items = (List<FeedItem>) 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; - } -} diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java deleted file mode 100644 index 671ac30d5..000000000 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ /dev/null @@ -1,1391 +0,0 @@ -package de.danoeh.antennapod.storage; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.MergeCursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import java.util.Arrays; -import java.util.List; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedComponent; -import de.danoeh.antennapod.feed.FeedImage; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedMedia; -import de.danoeh.antennapod.feed.FeedPreferences; -import de.danoeh.antennapod.service.download.DownloadStatus; -import de.danoeh.antennapod.util.flattr.FlattrStatus; - -// 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 = 12; - public 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; - public static final int KEY_FEED_FLATTR_STATUS_INDEX = 14; - public static final int KEY_FEED_USERNAME_INDEX = 15; - public static final int KEY_FEED_PASSWORD_INDEX = 16; - // ----------- 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; - public static final int KEY_ITEM_FLATTR_STATUS_INDEX = 12; - // ---------- 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; - public static final int KEY_PLAYED_DURATION_INDEX = 10; - // --------- 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_FLATTR_STATUS = "flattr_status"; - 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"; - public static final String KEY_AUTO_DOWNLOAD = "auto_download"; - public static final String KEY_PLAYED_DURATION = "played_duration"; - public static final String KEY_USERNAME = "username"; - public static final String KEY_PASSWORD = "password"; - - // 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," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1," - + KEY_FLATTR_STATUS + " INTEGER," - + KEY_USERNAME + " TEXT," - + KEY_PASSWORD + " 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," - + KEY_FLATTR_STATUS + " INTEGER," - + KEY_IMAGE + " INTEGER)"; - - 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," - + KEY_PLAYED_DURATION + " 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 feed-table - */ - private static final String[] FEED_SEL_STD = { - TABLE_NAME_FEEDS + "." + KEY_ID, - TABLE_NAME_FEEDS + "." + KEY_TITLE, - TABLE_NAME_FEEDS + "." + KEY_FILE_URL, - TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL, - TABLE_NAME_FEEDS + "." + KEY_DOWNLOADED, - TABLE_NAME_FEEDS + "." + KEY_LINK, - TABLE_NAME_FEEDS + "." + KEY_DESCRIPTION, - TABLE_NAME_FEEDS + "." + KEY_PAYMENT_LINK, - TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE, - TABLE_NAME_FEEDS + "." + KEY_LANGUAGE, - TABLE_NAME_FEEDS + "." + KEY_AUTHOR, - TABLE_NAME_FEEDS + "." + KEY_IMAGE, - TABLE_NAME_FEEDS + "." + KEY_TYPE, - TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER, - TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD, - TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS, - TABLE_NAME_FEEDS + "." + KEY_USERNAME, - TABLE_NAME_FEEDS + "." + KEY_PASSWORD - }; - - // column indices for FEED_SEL_STD - public static final int IDX_FEED_SEL_STD_ID = 0; - public static final int IDX_FEED_SEL_STD_TITLE = 1; - public static final int IDX_FEED_SEL_STD_FILE_URL = 2; - public static final int IDX_FEED_SEL_STD_DOWNLOAD_URL = 3; - public static final int IDX_FEED_SEL_STD_DOWNLOADED = 4; - public static final int IDX_FEED_SEL_STD_LINK = 5; - public static final int IDX_FEED_SEL_STD_DESCRIPTION = 6; - public static final int IDX_FEED_SEL_STD_PAYMENT_LINK = 7; - public static final int IDX_FEED_SEL_STD_LASTUPDATE = 8; - public static final int IDX_FEED_SEL_STD_LANGUAGE = 9; - public static final int IDX_FEED_SEL_STD_AUTHOR = 10; - public static final int IDX_FEED_SEL_STD_IMAGE = 11; - public static final int IDX_FEED_SEL_STD_TYPE = 12; - public static final int IDX_FEED_SEL_STD_FEED_IDENTIFIER = 13; - public static final int IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD = 14; - public static final int IDX_FEED_SEL_STD_FLATTR_STATUS = 15; - public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 16; - public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 17; - - - /** - * Select all columns from the feeditems-table except description and - * content-encoded. - */ - private static final String[] FEEDITEM_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, - TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS, - TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE}; - - /** - * Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries. - */ - private static final String SEL_FI_SMALL_STR; - - static { - String selFiSmall = Arrays.toString(FEEDITEM_SEL_FI_SMALL); - SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1); - } - - // column indices for FEEDITEM_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; - public static final int IDX_FI_SMALL_FLATTR_STATUS = 10; - public static final int IDX_FI_SMALL_IMAGE = 11; - - /** - * Select id, description and content-encoded column from feeditems. - */ - private 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; - - 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 = getDbHelperSingleton(c.getApplicationContext()); - } - - public PodDBAdapter open() { - if (db == null || !db.isOpen() || db.isReadOnly()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Opening DB"); - try { - db = helper.getWritableDatabase(); - } catch (SQLException ex) { - ex.printStackTrace(); - db = helper.getReadableDatabase(); - } - } - return this; - } - - public void close() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Closing DB"); - //db.close(); - } - - public static boolean deleteDatabase(Context context) { - Log.w(TAG, "Deleting database"); - dbHelperSingleton.close(); - dbHelperSingleton = null; - return context.deleteDatabase(DATABASE_NAME); - } - - /** - * 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()); - - Log.d(TAG, "Setting feed with flattr status " + feed.getTitle() + ": " + feed.getFlattrStatus().toLong()); - - values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); - if (feed.getId() == 0) { - // Create new entry - if (BuildConfig.DEBUG) - Log.d(this.toString(), "Inserting new Feed into db"); - feed.setId(db.insert(TABLE_NAME_FEEDS, null, values)); - } else { - if (BuildConfig.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(); - } - - public void setFeedPreferences(FeedPreferences prefs) { - if (prefs.getFeedID() == 0) { - throw new IllegalArgumentException("Feed ID of preference must not be null"); - } - ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload()); - values.put(KEY_USERNAME, prefs.getUsername()); - values.put(KEY_PASSWORD, prefs.getPassword()); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())}); - } - - /** - * 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())}); - } - - final FeedComponent owner = image.getOwner(); - if (owner != null && owner.getId() != 0) { - values.clear(); - values.put(KEY_IMAGE, image.getId()); - if (owner instanceof Feed) { - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().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); - } - 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(); - } - - public void setFeedMediaPlaybackInformation(FeedMedia media) { - if (media.getId() != 0) { - ContentValues values = new ContentValues(); - values.put(KEY_POSITION, media.getPosition()); - values.put(KEY_DURATION, media.getDuration()); - values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } else { - Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0"); - } - } - - public void setFeedMediaPlaybackCompletionDate(FeedMedia media) { - if (media.getId() != 0) { - ContentValues values = new ContentValues(); - values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime()); - values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } else { - Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0"); - } - } - - /** - * Insert all FeedItems of a feed and the feed object itself in a single - * transaction - */ - public void setCompleteFeed(Feed... feeds) { - db.beginTransaction(); - for (Feed feed : feeds) { - setFeed(feed); - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - setFeedItem(item, false); - } - } - if (feed.getPreferences() != null) { - setFeedPreferences(feed.getPreferences()); - } - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - /** - * Update the flattr status of a feed - */ - public void setFeedFlattrStatus(Feed feed) { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); - } - - /** - * Get all feeds in the flattr queue. - */ - public Cursor getFeedsInFlattrQueueCursor() { - return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_FLATTR_STATUS + "=?", - new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null); - } - - /** - * Get all feed items in the flattr queue. - */ - public Cursor getFeedItemsInFlattrQueueCursor() { - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FLATTR_STATUS + "=?", - new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null); - } - - /** - * Counts feeds and feed items in the flattr queue - */ - public int getFlattrQueueSize() { - int res = 0; - Cursor c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", - TABLE_NAME_FEEDS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); - if (c.moveToFirst()) { - res = c.getInt(0); - c.close(); - } else { - Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feeds"); - } - c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", - TABLE_NAME_FEED_ITEMS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); - if (c.moveToFirst()) { - res += c.getInt(0); - c.close(); - } else { - Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feed items"); - } - - return res; - } - - /** - * Updates the download URL of a Feed. - */ - public void setFeedDownloadUrl(String original, String updated) { - ContentValues values = new ContentValues(); - values.put(KEY_DOWNLOAD_URL, updated); - db.update(TABLE_NAME_FEEDS, values, KEY_DOWNLOAD_URL + "=?", new String[]{original}); - } - - public void setFeedItemlist(List<FeedItem> items) { - db.beginTransaction(); - for (FeedItem item : items) { - setFeedItem(item, true); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public long setSingleFeedItem(FeedItem item) { - db.beginTransaction(); - long result = setFeedItem(item, true); - db.setTransactionSuccessful(); - db.endTransaction(); - return result; - } - - /** - * Update the flattr status of a FeedItem - */ - public void setFeedItemFlattrStatus(FeedItem feedItem) { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, feedItem.getFlattrStatus().toLong()); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(feedItem.getId())}); - } - - /** - * Update the flattr status of a feed or feed item specified by its payment link - * and the new flattr status to use - */ - public void setItemFlattrStatus(String url, FlattrStatus status) { - //Log.d(TAG, "setItemFlattrStatus(" + url + ") = " + status.toString()); - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, status.toLong()); - - // regexps in sqlite would be neat! - String[] query_urls = new String[]{ - "*" + url + "&*", - "*" + url + "%2F&*", - "*" + url + "", - "*" + url + "%2F" - }; - - if (db.update(TABLE_NAME_FEEDS, values, - KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls - ) > 0) { - Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in Feeds table"); - return; - } - if (db.update(TABLE_NAME_FEED_ITEMS, values, - KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls - ) > 0) { - Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in FeedsItems table"); - } - } - - /** - * Reset flattr status to unflattrd for all items - */ - public void clearAllFlattrStatus() { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, 0); - db.update(TABLE_NAME_FEEDS, values, null, null); - db.update(TABLE_NAME_FEED_ITEMS, values, null, null); - } - - /** - * Inserts or updates a feeditem entry - * - * @param item The FeedItem - * @param saveFeed true if the Feed of the item should also be saved. This should be set to - * false if the method is executed on a list of FeedItems of the same Feed. - * @return the id of the entry - */ - private long setFeedItem(FeedItem item, boolean saveFeed) { - 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 (saveFeed && item.getFeed() != null) { - 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()); - values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong()); - if (item.hasItemImage()) { - if (item.getImage().getId() == 0) { - setImage(item.getImage()); - } - values.put(KEY_IMAGE, item.getImage().getId()); - } - - 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) { - 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, KEY_ID + "=?", new String[]{String.valueOf(itemId)}); - - if (resetMediaPosition) { - values.clear(); - values.put(KEY_POSITION, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(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, KEY_ID + "=?", new String[]{String.valueOf(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().getCode()); - 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() { - final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG); - Cursor result = db.rawQuery(query, null); - long count = 0; - if (result.moveToFirst()) { - count = result.getLong(0); - } - result.close(); - return count; - } - - public void removeDownloadLogItems(long count) { - if (count > 0) { - final String sql = String.format("DELETE FROM %s WHERE %s in (SELECT %s from %s ORDER BY %s ASC LIMIT %d)", - TABLE_NAME_DOWNLOAD_LOG, KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG, KEY_COMPLETION_DATE, count); - db.execSQL(sql, null); - } - } - - public void setQueue(List<FeedItem> 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); - } - if (item.hasItemImage()) { - removeFeedImage(item.getImage()); - } - 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()); - } - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - 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() { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, null, null, null, null, - KEY_TITLE + " COLLATE NOCASE ASC"); - return c; - } - - public final Cursor getFeedCursorDownloadUrls() { - return db.query(TABLE_NAME_FEEDS, new String[]{KEY_ID, KEY_DOWNLOAD_URL}, null, null, null, null, null); - } - - public final Cursor getExpiredFeedsCursor(long expirationTime) { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_LASTUPDATE + " < " + String.valueOf(System.currentTimeMillis() - expirationTime), - null, null, null, - null); - return c; - } - - /** - * Returns a cursor with all FeedItems of a Feed. Uses FEEDITEM_SEL_FI_SMALL - * - * @param feed The feed you want to get the FeedItems from. - * @return The cursor of the query - */ - public final Cursor getAllItemsOfFeedCursor(final Feed feed) { - return getAllItemsOfFeedCursor(feed.getId()); - } - - public final Cursor getAllItemsOfFeedCursor(final long feedId) { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED - + "=?", new String[]{String.valueOf(feedId)}, null, null, - null - ); - return c; - } - - /** - * Return a cursor with the SEL_FI_EXTRA selection of a single feeditem. - */ - public final Cursor getExtraInformationOfItem(final FeedItem item) { - Cursor c = db - .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?", - new String[]{String.valueOf(item.getId())}, null, - null, null); - return c; - } - - /** - * Returns a cursor for a DB query in the FeedMedia table for a given ID. - * - * @param item The item you want to get the FeedMedia from - * @return The cursor of the query - */ - public final Cursor getFeedMediaOfItemCursor(final FeedItem item) { - Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", - new String[]{String.valueOf(item.getMedia().getId())}, null, - null, null); - return c; - } - - /** - * Returns a cursor for a DB query in the FeedImages table for a given ID. - * - * @param id ID of the FeedImage - * @return The cursor of the query - */ - public final Cursor getImageCursor(final long id) { - Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?", - new String[]{String.valueOf(id)}, null, null, null); - return c; - } - - public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) { - Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM - + "=?", new String[]{String.valueOf(item.getId())}, null, - null, null - ); - return c; - } - - public final Cursor getDownloadLogCursor(final int limit) { - Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null, - null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit); - return c; - } - - /** - * Returns a cursor which contains all feed items in the queue. The returned - * cursor uses the FEEDITEM_SEL_FI_SMALL selection. - */ - public final Cursor getQueueCursor() { - Object[] args = (Object[]) new String[]{ - 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, - 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); - /* - * Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_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; - } - - public Cursor getQueueIDCursor() { - 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 FEEDITEM_SEL_FI_SMALL selection. - */ - public final Cursor getUnreadItemsCursor() { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_READ - + "=0", null, null, null, KEY_PUBDATE + " DESC"); - return c; - } - - public final Cursor getUnreadItemIdsCursor() { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID}, - KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC"); - return c; - - } - - public final Cursor getRecentlyPublishedItemsCursor(int limit) { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, null, null, null, null, KEY_PUBDATE + " DESC LIMIT " + limit); - return c; - } - - public Cursor getDownloadedItemsCursor() { - final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS - + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " - + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" - + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + " WHERE " - + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0"; - Cursor c = db.rawQuery(query, null); - return c; - } - - /** - * Returns a cursor which contains feed media objects with a playback - * completion date in ascending 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) { - Validate.isTrue(limit >= 0, "Limit must be >= 0"); - - Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, - KEY_PLAYBACK_COMPLETION_DATE + " > 0 LIMIT " + limit, null, null, - null, null); - return c; - } - - public final Cursor getSingleFeedMediaCursor(long id) { - 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) { - 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) { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, 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 - ); - } - - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_ID + " IN " - + buildInOperator(ids.length), ids, null, null, null); - - } - - public int getQueueSize() { - final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE); - Cursor c = db.rawQuery(query, null); - int result = 0; - if (c.moveToFirst()) { - result = c.getInt(0); - } - c.close(); - return result; - } - - 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"; - - Cursor c = db.rawQuery(query, null); - int result = 0; - if (c.moveToFirst()) { - 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, FEEDITEM_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, FEEDITEM_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, FEEDITEM_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, FEEDITEM_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, FEEDITEM_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, FEEDITEM_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); - } - - - 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 Feeds.id, num_items, new_items, latest_episode, in_progress FROM " + - " Feeds LEFT JOIN " + - "(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 LEFT JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" + - " ON Feeds.id = feed ORDER BY Feeds.title COLLATE NOCASE ASC;"; - - 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); - } - - @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"); - } - 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, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); - contentValues.clear(); - } while (feeditemCursor.moveToNext()); - db.setTransactionSuccessful(); - db.endTransaction(); - } - feeditemCursor.close(); - } - if (oldVersion <= 9) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_AUTO_DOWNLOAD - + " INTEGER DEFAULT 1"); - } - if (oldVersion <= 10) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_PLAYED_DURATION - + " INTEGER"); - } - if (oldVersion <= 11) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_USERNAME - + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_PASSWORD - + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_IMAGE - + " INTEGER"); - } - } - } -} |