Implemented refresh, auto-download, auto-cleanup methods

This commit is contained in:
daniel oeh 2013-05-26 03:47:57 +02:00
parent a704a33e2b
commit c6545a5643
4 changed files with 366 additions and 16 deletions

View File

@ -48,6 +48,26 @@ public final class DBReader {
return feeds; 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, public static void loadFeedDataOfFeedItemlist(Context context,
List<FeedItem> items) { List<FeedItem> items) {
List<Feed> feeds = getFeedList(context); List<Feed> feeds = getFeedList(context);
@ -272,6 +292,25 @@ public final class DBReader {
return items; 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) { public static List<FeedItem> getUnreadItemsList(Context context) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Extracting unread items list"); Log.d(TAG, "Extracting unread items list");
@ -411,6 +450,14 @@ public final class DBReader {
} }
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. * Searches the DB for a FeedImage of the given id.
* *

View File

@ -1,16 +1,28 @@
package de.danoeh.antennapod.storage; 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.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService; 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; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
public final class DBTasks { 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) { 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, 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, public static void notifyMissingFeedMediaFile(final Context context,
final FeedMedia media) { 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) { 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)
static void downloadFeedItem(boolean performAutoCleanup,
final Context context, final long... itemIds)
throws DownloadRequestException { 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());
}
}
}
} }
public static void autodownloadUndownloadedItems(Context context) { 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;
} }
private static int getPerformAutoCleanupArgs(final int episodeNumber) { 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(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; return 0;
} }
public static void performAutoCleanup(final Context context) { public static void performAutoCleanup(final Context context) {
performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
} }
private static int performAutoCleanup(final Context context, private static int performAutoCleanup(final Context context,
final int episodeNumber) { 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) { public static void enqueueAllNewItems(final Context context) {
@ -144,8 +417,8 @@ public final class DBTasks {
return null; return null;
} }
public static synchronized Feed updateFeed(final Context context,
public static synchronized Feed updateFeed(final Context context, final Feed newFeed) { final Feed newFeed) {
// Look up feed in the feedslist // Look up feed in the feedslist
final Feed savedFeed = searchFeedByIdentifyingValue(context, final Feed savedFeed = searchFeedByIdentifyingValue(context,
newFeed.getIdentifyingValue()); newFeed.getIdentifyingValue());

View File

@ -506,7 +506,7 @@ public class DBWriter {
} }
private static void setFeedMedia(final Context context, static void setFeedMedia(final Context context,
final FeedMedia media) { final FeedMedia media) {
dbExec.submit(new Runnable() { dbExec.submit(new Runnable() {

View File

@ -10,6 +10,7 @@ import android.database.DatabaseUtils;
import android.database.MergeCursor; import android.database.MergeCursor;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
@ -574,6 +575,14 @@ public class PodDBAdapter {
return c; 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 * 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 * Returns a cursor which contains feed media objects with a playback
* completion date in descending order. * 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 * Uses DatabaseUtils to escape a search query and removes ' at the
* beginning and the end of the string returned by the escape method. * beginning and the end of the string returned by the escape method.