mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-01-28 01:19:23 +01:00
Made auto-cleanup and auto-download methods changeable
This commit is contained in:
parent
066cab8da5
commit
3c473c490b
@ -16,5 +16,6 @@ public class ClientConfigurator {
|
|||||||
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
||||||
ClientConfig.storageCallbacks = new StorageCallbacksImpl();
|
ClientConfig.storageCallbacks = new StorageCallbacksImpl();
|
||||||
ClientConfig.flattrCallbacks = new FlattrCallbacksImpl();
|
ClientConfig.flattrCallbacks = new FlattrCallbacksImpl();
|
||||||
|
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package de.danoeh.antennapod.config;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.DBTasksCallbacks;
|
||||||
|
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
|
||||||
|
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
|
||||||
|
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||||
|
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||||
|
|
||||||
|
public class DBTasksCallbacksImpl implements DBTasksCallbacks {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
|
||||||
|
return new APDownloadAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
|
||||||
|
return new APCleanupAlgorithm();
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,6 @@ public class ClientConfig {
|
|||||||
public static FlattrCallbacks flattrCallbacks;
|
public static FlattrCallbacks flattrCallbacks;
|
||||||
|
|
||||||
public static StorageCallbacks storageCallbacks;
|
public static StorageCallbacks storageCallbacks;
|
||||||
|
|
||||||
|
public static DBTasksCallbacks dbTasksCallbacks;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package de.danoeh.antennapod.core;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||||
|
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks for the DBTasks class of the storage module.
|
||||||
|
*/
|
||||||
|
public interface DBTasksCallbacks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's implementation of the AutomaticDownloadAlgorithm interface.
|
||||||
|
*/
|
||||||
|
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's implementation of the EpisodeCacheCleanupAlgorithm interface.
|
||||||
|
*/
|
||||||
|
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm();
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
|
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
|
||||||
|
*/
|
||||||
|
public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
|
||||||
|
private static final String TAG = "APCleanupAlgorithm";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int performCleanup(Context context, Integer 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 | ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = delete.size();
|
||||||
|
|
||||||
|
|
||||||
|
Log.i(TAG, String.format(
|
||||||
|
"Auto-delete deleted %d episodes (%d requested)", counter,
|
||||||
|
episodeNumber));
|
||||||
|
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getDefaultCleanupParameter(Context context) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
|
||||||
|
return getPerformAutoCleanupArgs(context, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.BuildConfig;
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
|
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||||
|
import de.danoeh.antennapod.core.util.PowerUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the automatic download algorithm used by AntennaPod. This class assumes that
|
||||||
|
* the client uses the APEpisodeCleanupAlgorithm.
|
||||||
|
*/
|
||||||
|
public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
|
||||||
|
private static final String TAG = "APDownloadAlgorithm";
|
||||||
|
|
||||||
|
private final APCleanupAlgorithm cleanupAlgorithm = new APCleanupAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
|
||||||
|
* 1. Network is available
|
||||||
|
* 2. The device is charging or the user allows auto download on battery
|
||||||
|
* 3. 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 Runnable that will be submitted to an ExecutorService.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
// true if we should auto download based on network status
|
||||||
|
boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable(context)
|
||||||
|
&& UserPreferences.isEnableAutodownload();
|
||||||
|
|
||||||
|
// true if we should auto download based on power status
|
||||||
|
boolean powerShouldAutoDl = PowerUtils.deviceCharging(context)
|
||||||
|
|| UserPreferences.isEnableAutodownloadOnBattery();
|
||||||
|
|
||||||
|
// we should only auto download if both network AND power are happy
|
||||||
|
if (networkShouldAutoDl && powerShouldAutoDl) {
|
||||||
|
|
||||||
|
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
|
||||||
|
|
||||||
|
final List<FeedItem> queue = DBReader.getQueue(context);
|
||||||
|
final List<FeedItem> unreadItems = DBReader
|
||||||
|
.getUnreadItemsList(context);
|
||||||
|
|
||||||
|
int undownloadedEpisodes = DBTasks.getNumberOfUndownloadedEpisodes(queue,
|
||||||
|
unreadItems);
|
||||||
|
int downloadedEpisodes = DBReader
|
||||||
|
.getNumberOfDownloadedEpisodes(context);
|
||||||
|
int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
|
||||||
|
APCleanupAlgorithm.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 {
|
||||||
|
DBTasks.downloadFeedItems(false, context,
|
||||||
|
itemsToDownload.toArray(new FeedItem[itemsToDownload
|
||||||
|
.size()])
|
||||||
|
);
|
||||||
|
} catch (DownloadRequestException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.BuildConfig;
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPodSP apps.
|
||||||
|
*/
|
||||||
|
public class APSPCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
|
||||||
|
private static final String TAG = "APSPCleanupAlgorithm";
|
||||||
|
|
||||||
|
final int numberOfNewAutomaticallyDownloadedEpisodes;
|
||||||
|
|
||||||
|
public APSPCleanupAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
|
||||||
|
this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an automatic cleanup. Episodes that have been downloaded first will also be deleted first.
|
||||||
|
* The episode that is currently playing as well as the n most recent episodes (the exact value is determined
|
||||||
|
* by AppPreferences.numberOfNewAutomaticallyDownloadedEpisodes) will never be deleted.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param episodeSize The maximum amount of space that should be freed by this method
|
||||||
|
* @return The number of episodes that have been deleted
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int performCleanup(Context context, Integer episodeSize) {
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, String.format("performAutoCleanup(%d)", episodeSize));
|
||||||
|
if (episodeSize <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FeedItem> candidates = getAutoCleanupCandidates(context);
|
||||||
|
List<FeedItem> deleteList = new ArrayList<FeedItem>();
|
||||||
|
long deletedEpisodesSize = 0;
|
||||||
|
Collections.sort(candidates, new Comparator<FeedItem>() {
|
||||||
|
@Override
|
||||||
|
public int compare(FeedItem lhs, FeedItem rhs) {
|
||||||
|
File lFile = new File(lhs.getMedia().getFile_url());
|
||||||
|
File rFile = new File(rhs.getMedia().getFile_url());
|
||||||
|
if (!lFile.exists() || !rFile.exists()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (FileUtils.isFileOlder(lFile, rFile)) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// listened episodes will be deleted first
|
||||||
|
Iterator<FeedItem> it = candidates.iterator();
|
||||||
|
for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
|
||||||
|
if (!i.getMedia().isPlaying() && i.getMedia().getPlaybackCompletionDate() != null) {
|
||||||
|
it.remove();
|
||||||
|
deleteList.add(i);
|
||||||
|
deletedEpisodesSize += i.getMedia().getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete unlistened old episodes if necessary
|
||||||
|
it = candidates.iterator();
|
||||||
|
for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
|
||||||
|
if (!i.getMedia().isPlaying()) {
|
||||||
|
it.remove();
|
||||||
|
deleteList.add(i);
|
||||||
|
deletedEpisodesSize += i.getMedia().getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (FeedItem item : deleteList) {
|
||||||
|
try {
|
||||||
|
DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Log.d(TAG, String.format("performAutoCleanup(%d) deleted %d episodes and free'd %d bytes of memory",
|
||||||
|
episodeSize, deleteList.size(), deletedEpisodesSize));
|
||||||
|
return deleteList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getDefaultCleanupParameter(Context context) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
|
||||||
|
int episodeSize = 0;
|
||||||
|
for (FeedItem item : items) {
|
||||||
|
if (item.hasMedia() && !item.getMedia().isDownloaded()) {
|
||||||
|
episodeSize += item.getMedia().getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return episodeSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of FeedItems that have been downloaded, but are not one of the
|
||||||
|
* [numberOfNewAutomaticallyDownloadedEpisodes] most recent items.
|
||||||
|
*/
|
||||||
|
private List<FeedItem> getAutoCleanupCandidates(Context context) {
|
||||||
|
List<FeedItem> downloaded = new ArrayList<FeedItem>(DBReader.getDownloadedItems(context));
|
||||||
|
List<FeedItem> recent = new ArrayList<FeedItem>(DBReader.getRecentlyPublishedEpisodes(context,
|
||||||
|
numberOfNewAutomaticallyDownloadedEpisodes));
|
||||||
|
for (FeedItem r : recent) {
|
||||||
|
if (r.hasMedia() && r.getMedia().isDownloaded()) {
|
||||||
|
for (int i = 0; i < downloaded.size(); i++) {
|
||||||
|
if (downloaded.get(i).getId() == r.getId()) {
|
||||||
|
downloaded.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloaded;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.BuildConfig;
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
|
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the automatic download algorithm used by AntennaPodSP apps.
|
||||||
|
*/
|
||||||
|
public class APSPDownloadAlgorithm implements AutomaticDownloadAlgorithm {
|
||||||
|
private static final String TAG = "APSPDownloadAlgorithm";
|
||||||
|
|
||||||
|
private final int numberOfNewAutomaticallyDownloadedEpisodes;
|
||||||
|
|
||||||
|
public APSPDownloadAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
|
||||||
|
this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the most recent episodes automatically. The exact number of
|
||||||
|
* episodes that will be downloaded can be set in the AppPreferences.
|
||||||
|
*
|
||||||
|
* @param context Used for accessing the DB.
|
||||||
|
* @return A Runnable that will be submitted to an ExecutorService.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
|
||||||
|
if (NetworkUtils.autodownloadNetworkAvailable(context)
|
||||||
|
&& UserPreferences.isEnableAutodownload()) {
|
||||||
|
List<FeedItem> itemsToDownload = DBReader.getRecentlyPublishedEpisodes(context,
|
||||||
|
numberOfNewAutomaticallyDownloadedEpisodes);
|
||||||
|
Iterator<FeedItem> it = itemsToDownload.iterator();
|
||||||
|
for (FeedItem item = it.next(); it.hasNext(); item = it.next()) {
|
||||||
|
if (item.getMedia().isDownloaded()) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Log.d(TAG, "Enqueueing " + itemsToDownload.size()
|
||||||
|
+ " items for automatic download");
|
||||||
|
if (!itemsToDownload.isEmpty()) {
|
||||||
|
try {
|
||||||
|
DBTasks.downloadFeedItems(false, context,
|
||||||
|
itemsToDownload.toArray(new FeedItem[itemsToDownload
|
||||||
|
.size()]));
|
||||||
|
} catch (DownloadRequestException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public interface AutomaticDownloadAlgorithm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for undownloaded episodes and request a download if
|
||||||
|
* 1. Network is available
|
||||||
|
* 2. The device is charging or the user allows auto download on battery
|
||||||
|
* 3. 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 Runnable that will be submitted to an ExecutorService.
|
||||||
|
*/
|
||||||
|
public Runnable autoDownloadUndownloadedItems(Context context, long... mediaIds);
|
||||||
|
}
|
@ -8,7 +8,6 @@ import android.util.Log;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -35,8 +34,6 @@ import de.danoeh.antennapod.core.service.GpodnetSyncService;
|
|||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||||
import de.danoeh.antennapod.core.util.DownloadError;
|
import de.danoeh.antennapod.core.util.DownloadError;
|
||||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
|
||||||
import de.danoeh.antennapod.core.util.PowerUtils;
|
|
||||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||||
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
||||||
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
|
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
|
||||||
@ -387,8 +384,8 @@ public final class DBTasks {
|
|||||||
downloadFeedItems(true, context, items);
|
downloadFeedItems(true, context, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadFeedItems(boolean performAutoCleanup,
|
static void downloadFeedItems(boolean performAutoCleanup,
|
||||||
final Context context, final FeedItem... items)
|
final Context context, final FeedItem... items)
|
||||||
throws DownloadRequestException {
|
throws DownloadRequestException {
|
||||||
final DownloadRequester requester = DownloadRequester.getInstance();
|
final DownloadRequester requester = DownloadRequester.getInstance();
|
||||||
|
|
||||||
@ -397,8 +394,10 @@ public final class DBTasks {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performAutoCleanup(context,
|
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
|
||||||
getPerformAutoCleanupArgs(context, items.length));
|
.performCleanup(context,
|
||||||
|
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
|
||||||
|
.getPerformCleanupParameter(context, Arrays.asList(items)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}.start();
|
}.start();
|
||||||
@ -428,7 +427,7 @@ public final class DBTasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getNumberOfUndownloadedEpisodes(
|
static int getNumberOfUndownloadedEpisodes(
|
||||||
final List<FeedItem> queue, final List<FeedItem> unreadItems) {
|
final List<FeedItem> queue, final List<FeedItem> unreadItems) {
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
for (FeedItem item : queue) {
|
for (FeedItem item : queue) {
|
||||||
@ -460,117 +459,9 @@ public final class DBTasks {
|
|||||||
* @return A Future that can be used for waiting for the methods completion.
|
* @return A Future that can be used for waiting for the methods completion.
|
||||||
*/
|
*/
|
||||||
public static Future<?> autodownloadUndownloadedItems(final Context context, final long... mediaIds) {
|
public static Future<?> autodownloadUndownloadedItems(final Context context, final long... mediaIds) {
|
||||||
return autodownloadExec.submit(new Runnable() {
|
return autodownloadExec.submit(ClientConfig.dbTasksCallbacks.getAutomaticDownloadAlgorithm()
|
||||||
@Override
|
.autoDownloadUndownloadedItems(context, mediaIds));
|
||||||
public void run() {
|
|
||||||
|
|
||||||
// true if we should auto download based on network status
|
|
||||||
boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable(context)
|
|
||||||
&& UserPreferences.isEnableAutodownload();
|
|
||||||
|
|
||||||
// true if we should auto download based on power status
|
|
||||||
boolean powerShouldAutoDl = PowerUtils.deviceCharging(context)
|
|
||||||
|| UserPreferences.isEnableAutodownloadOnBattery();
|
|
||||||
|
|
||||||
// we should only auto download if both network AND power are happy
|
|
||||||
if (networkShouldAutoDl && powerShouldAutoDl) {
|
|
||||||
|
|
||||||
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -582,63 +473,8 @@ public final class DBTasks {
|
|||||||
* @param context Used for accessing the DB.
|
* @param context Used for accessing the DB.
|
||||||
*/
|
*/
|
||||||
public static void performAutoCleanup(final Context context) {
|
public static void performAutoCleanup(final Context context) {
|
||||||
performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
|
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context,
|
||||||
}
|
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter(context));
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
|
||||||
|
public interface EpisodeCleanupAlgorithm<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes downloaded episodes that are no longer needed. What episodes are deleted and how many
|
||||||
|
* of them depends on the implementation.
|
||||||
|
*
|
||||||
|
* @param context Can be used for accessing the database
|
||||||
|
* @param parameter An additional parameter. This parameter is either returned by getDefaultCleanupParameter
|
||||||
|
* or getPerformCleanupParameter.
|
||||||
|
* @return The number of episodes that were deleted.
|
||||||
|
*/
|
||||||
|
public int performCleanup(Context context, T parameter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a parameter for performCleanup. The implementation of this interface should decide how much
|
||||||
|
* space to free to satisfy the episode cache conditions. If the conditions are already satisfied, this
|
||||||
|
* method should not have any effects.
|
||||||
|
*/
|
||||||
|
public T getDefaultCleanupParameter(Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a parameter for performCleanup.
|
||||||
|
*
|
||||||
|
* @param items A list of FeedItems that are about to be downloaded. The implementation of this interface
|
||||||
|
* should decide how much space to free to satisfy the episode cache conditions.
|
||||||
|
*/
|
||||||
|
public T getPerformCleanupParameter(Context context, List<FeedItem> items);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user