summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/de/danoeh/antennapod/storage/DBReader.java47
-rw-r--r--src/de/danoeh/antennapod/storage/DBTasks.java301
-rw-r--r--src/de/danoeh/antennapod/storage/DBWriter.java2
-rw-r--r--src/de/danoeh/antennapod/storage/PodDBAdapter.java30
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.