diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2013-05-26 03:47:57 +0200 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2013-05-26 03:47:57 +0200 |
commit | c6545a5643886b653422fcd1f722fdb1d34f6f9e (patch) | |
tree | e2fa11c0d51cc7da8cdfa9c94cee2aeea3c0955f /src/de | |
parent | a704a33e2b31a7312f35f33251faaf1017b982d9 (diff) | |
download | AntennaPod-c6545a5643886b653422fcd1f722fdb1d34f6f9e.zip |
Implemented refresh, auto-download, auto-cleanup methods
Diffstat (limited to 'src/de')
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBReader.java | 47 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBTasks.java | 301 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBWriter.java | 2 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/PodDBAdapter.java | 30 |
4 files changed, 365 insertions, 15 deletions
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index e731a360e..5ae47f32b 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -47,6 +47,26 @@ public final class DBReader { feedlistCursor.close(); return feeds; } + + static List<Feed> getExpiredFeedsList(final Context context, final long expirationTime) { + if (AppConfig.DEBUG) + Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); + List<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; + } public static void loadFeedDataOfFeedItemlist(Context context, List<FeedItem> items) { @@ -271,6 +291,25 @@ public final class DBReader { adapter.close(); return items; } + + public static List<FeedItem> getDownloadedItems(Context context) { + if (AppConfig.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; + + } public static List<FeedItem> getUnreadItemsList(Context context) { if (AppConfig.DEBUG) @@ -410,6 +449,14 @@ public final class DBReader { return item; } + + public static int getNumberOfDownloadedEpisodes(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final int result = adapter.getNumberOfDownloadedEpisodes(); + adapter.close(); + return result; + } /** * Searches the DB for a FeedImage of the given id. diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index d71a5498b..9b874768c 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -1,16 +1,28 @@ package de.danoeh.antennapod.storage; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import android.content.Context; import android.content.Intent; import android.util.Log; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.util.DownloadError; +import de.danoeh.antennapod.util.NetworkUtils; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; public final class DBTasks { @@ -55,47 +67,308 @@ public final class DBTasks { } } - public static void refreshAllFeeds(final Context context) { + private static ReentrantLock refreshAllFeedsLock = new ReentrantLock(); + + public static void refreshAllFeeds(final Context context, + final List<Feed> feeds) { + if (refreshAllFeedsLock.tryLock()) { + new Thread() { + public void run() { + refreshFeeds(context, feeds); + refreshAllFeedsLock.unlock(); + } + }.start(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, + "Ignoring request to refresh all feeds: Refresh lock is locked"); + } } public void refreshExpiredFeeds(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Refreshing expired feeds"); + + new Thread() { + public void run() { + long millis = UserPreferences.getUpdateInterval(); + + if (millis > 0) { + long now = Calendar.getInstance().getTime().getTime(); + + // Allow a 10 minute window + millis -= 10 * 60 * 1000; + List<Feed> feedList = DBReader.getExpiredFeedsList(context, + now - millis); + if (feedList.size() > 0) { + refreshFeeds(context, feedList); + } + } + } + }.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. */ + private static void refreshFeed(Context context, Feed feed) + throws DownloadRequestException { + DownloadRequester.getInstance().downloadFeed(context, + new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); } public static void notifyInvalidImageFile(final Context context, - final long imageId) { + final FeedImage image) { + Log.i(TAG, + "The feedmanager was notified about an invalid image download. It will now try to redownload the image file"); + try { + DownloadRequester.getInstance().downloadImage(context, image); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Log.w(TAG, "Failed to download invalid feed image"); + } } public static void notifyMissingFeedMediaFile(final Context context, final FeedMedia media) { + Log.i(TAG, + "The feedmanager was notified about a missing episode. It will update its database now."); + media.setDownloaded(false); + media.setFile_url(null); + DBWriter.setFeedMedia(context, media); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); } public static void downloadAllItemsInQueue(final Context context) { + new Thread() { + public void run() { + List<FeedItem> queue = DBReader.getQueue(context); + if (!queue.isEmpty()) { + try { + downloadFeedItems(context, + queue.toArray(new FeedItem[queue.size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + } + } + }.start(); } - public static void refreshFeed(final Context context, final long feedId) { + public static void downloadFeedItems(final Context context, + FeedItem... items) throws DownloadRequestException { + downloadFeedItems(true, context, items); } - public static void downloadFeedItem(final Context context, long... itemIds) { + private static void downloadFeedItems(boolean performAutoCleanup, + final Context context, final FeedItem... items) + throws DownloadRequestException { + final DownloadRequester requester = DownloadRequester.getInstance(); + + if (performAutoCleanup) { + new Thread() { + + @Override + public void run() { + performAutoCleanup(context, + getPerformAutoCleanupArgs(context, items.length)); + } + + }.start(); + } + for (FeedItem item : items) { + if (item.getMedia() != null + && !requester.isDownloadingFile(item.getMedia()) + && !item.getMedia().isDownloaded()) { + if (items.length > 1) { + try { + requester.downloadMedia(context, item.getMedia()); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus(context, + new DownloadStatus(item.getMedia(), item + .getMedia() + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, + false, e.getMessage())); + } + } else { + requester.downloadMedia(context, item.getMedia()); + } + } + } } - static void downloadFeedItem(boolean performAutoCleanup, - final Context context, final long... itemIds) - throws DownloadRequestException { + private static int getNumberOfUndownloadedEpisodes( + final List<FeedItem> queue, final List<FeedItem> unreadItems) { + int counter = 0; + for (FeedItem item : queue) { + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + counter++; + } + } + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + counter++; + } + } + return counter; } - public static void autodownloadUndownloadedItems(Context context) { + public static void autodownloadUndownloadedItems(final Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Performing auto-dl of undownloaded episodes"); + if (NetworkUtils.autodownloadNetworkAvailable(context) + && UserPreferences.isEnableAutodownload()) { + final List<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); + } + + 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); + if (item.hasMedia() && !item.getMedia().isDownloaded() + && !item.getMedia().isPlaying()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { + for (FeedItem item : unreadItems) { + if (item.hasMedia() && !item.getMedia().isDownloaded()) { + itemsToDownload.add(item); + episodeSpaceLeft--; + undownloadedEpisodes--; + if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { + break; + } + } + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Enqueueing " + itemsToDownload.size() + + " items for download"); + + try { + downloadFeedItems(false, context, + itemsToDownload.toArray(new FeedItem[itemsToDownload + .size()])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + + } } - private static int getPerformAutoCleanupArgs(final int episodeNumber) { + private static int getPerformAutoCleanupArgs(Context context, + final int episodeNumber) { + if (episodeNumber >= 0 + && UserPreferences.getEpisodeCacheSize() != UserPreferences + .getEpisodeCacheSizeUnlimited()) { + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(context); + if (downloadedEpisodes + episodeNumber >= UserPreferences + .getEpisodeCacheSize()) { + + return downloadedEpisodes + episodeNumber + - UserPreferences.getEpisodeCacheSize(); + } + } return 0; } public static void performAutoCleanup(final Context context) { + performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); } private static int performAutoCleanup(final Context context, final int episodeNumber) { - return 0; + List<FeedItem> candidates = DBReader.getDownloadedItems(context); + List<FeedItem> queue = DBReader.getQueue(context); + List<FeedItem> delete; + for (FeedItem item : candidates) { + if (item.hasMedia() && item.getMedia().isDownloaded() + && !queue.contains(item) && 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) { + DBWriter.deleteFeedMediaOfItem(context, item.getId()); + } + + int counter = delete.size(); + + if (AppConfig.DEBUG) + Log.d(TAG, String.format( + "Auto-delete deleted %d episodes (%d requested)", counter, + episodeNumber)); + + return counter; } public static void enqueueAllNewItems(final Context context) { @@ -132,7 +405,7 @@ public final class DBTasks { } return null; } - + /** Get a FeedItem by its identifying value. */ private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, String identifier) { @@ -144,8 +417,8 @@ public final class DBTasks { return null; } - - public static synchronized Feed updateFeed(final Context context, final Feed newFeed) { + public static synchronized Feed updateFeed(final Context context, + final Feed newFeed) { // Look up feed in the feedslist final Feed savedFeed = searchFeedByIdentifyingValue(context, newFeed.getIdentifyingValue()); @@ -161,7 +434,7 @@ public final class DBTasks { if (AppConfig.DEBUG) Log.d(TAG, "Feed with title " + newFeed.getTitle() + " already exists. Syncing new with existing one."); - + savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); if (savedFeed.compareWithOther(newFeed)) { if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 1a2468782..b5f9acdad 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -506,7 +506,7 @@ public class DBWriter { } - private static void setFeedMedia(final Context context, + static void setFeedMedia(final Context context, final FeedMedia media) { dbExec.submit(new Runnable() { diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index dcc9533a5..6ff22cb55 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -10,6 +10,7 @@ import android.database.DatabaseUtils; import android.database.MergeCursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; @@ -574,6 +575,14 @@ public class PodDBAdapter { return c; } + public final Cursor getExpiredFeedsCursor(long expirationTime) { + open(); + Cursor c = db.query(TABLE_NAME_FEEDS, null, "?<?", new String[] { + KEY_LASTUPDATE, Long.toString(expirationTime) }, null, null, + null); + return c; + } + /** * Returns a cursor with all FeedItems of a Feed. Uses SEL_FI_SMALL * @@ -680,6 +689,17 @@ public class PodDBAdapter { } + public Cursor getDownloadedItemsCursor() { + open(); + Cursor c = db.rawQuery("SELECT ? FROM " + TABLE_NAME_FEED_ITEMS + + "FULL JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " WHERE " + + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0", + SEL_FI_SMALL); + return c; + } + /** * Returns a cursor which contains feed media objects with a playback * completion date in descending order. @@ -766,6 +786,16 @@ public class PodDBAdapter { } + public final int getNumberOfDownloadedEpisodes() { + + Cursor c = db.rawQuery( + "SELECT COUNT(DISTINCT ?) AS count FROM ? WHERE ?>0", + new String[] { KEY_ID, TABLE_NAME_FEED_MEDIA, KEY_DOWNLOADED }); + final int result = c.getInt(0); + c.close(); + return result; + } + /** * Uses DatabaseUtils to escape a search query and removes ' at the * beginning and the end of the string returned by the escape method. |