diff options
Diffstat (limited to 'core/src/main')
10 files changed, 244 insertions, 56 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 59f8e29e1..4561c9bad 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -27,6 +27,10 @@ import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver; +import de.danoeh.antennapod.core.storage.APCleanupAlgorithm; +import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm; +import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm; +import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; /** * Provides access to preferences set by the user in the settings screen. A @@ -67,6 +71,7 @@ public class UserPreferences { // Network public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall"; public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; + public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup"; public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads"; public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; @@ -93,6 +98,9 @@ public class UserPreferences { // Experimental public static final String PREF_SONIC = "prefSonic"; public static final String PREF_NORMALIZER = "prefNormalizer"; + public static final int EPISODE_CLEANUP_QUEUE = -1; + public static final int EPISODE_CLEANUP_NULL = -2; + public static final int EPISODE_CLEANUP_DEFAULT = 0; // Constants private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; @@ -487,6 +495,18 @@ public class UserPreferences { .apply(); } + + public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() { + int cleanupValue = Integer.valueOf(prefs.getString(PREF_EPISODE_CLEANUP, "-1")); + if (cleanupValue == EPISODE_CLEANUP_QUEUE) { + return new APQueueCleanupAlgorithm(); + } else if (cleanupValue == EPISODE_CLEANUP_NULL) { + return new APNullCleanupAlgorithm(); + } else { + return new APCleanupAlgorithm(cleanupValue); + } + } + /** * Return the folder where the app stores all of its data. This method will * return the standard data folder if none has been set by the user. @@ -646,5 +666,4 @@ public class UserPreferences { public static int readEpisodeCacheSize(String valueFromPrefs) { return readEpisodeCacheSizeInternal(valueFromPrefs); } - } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java index 70b3aa90a..0dc54fb6e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java @@ -4,34 +4,53 @@ import android.content.Context; import android.util.Log; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; 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.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.LongList; /** * Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod. */ -public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> { +public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm { private static final String TAG = "APCleanupAlgorithm"; + /** the number of days after playback to wait before an item is eligible to be cleaned up */ + private final int numberOfDaysAfterPlayback; + + public APCleanupAlgorithm(int numberOfDaysAfterPlayback) { + this.numberOfDaysAfterPlayback = numberOfDaysAfterPlayback; + } @Override - public int performCleanup(Context context, Integer episodeNumber) { + public int performCleanup(Context context, int numberOfEpisodesToDelete) { List<FeedItem> candidates = new ArrayList<>(); List<FeedItem> downloadedItems = DBReader.getDownloadedItems(); - LongList queue = DBReader.getQueueIDList(); List<FeedItem> delete; + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_MONTH, -1 * numberOfDaysAfterPlayback); + Date mostRecentDateForDeletion = cal.getTime(); for (FeedItem item : downloadedItems) { - if (item.hasMedia() && item.getMedia().isDownloaded() - && !queue.contains(item.getId()) && item.isPlayed()) { - candidates.add(item); + if (item.hasMedia() + && item.getMedia().isDownloaded() + && !item.isTagged(FeedItem.TAG_QUEUE) + && item.isPlayed() + && !item.isTagged(FeedItem.TAG_FAVORITE)) { + FeedMedia media = item.getMedia(); + // make sure this candidate was played at least the proper amount of days prior + // to now + if (media != null + && media.getPlaybackCompletionDate() != null + && media.getPlaybackCompletionDate().before(mostRecentDateForDeletion)) { + candidates.add(item); + } } - } Collections.sort(candidates, (lhs, rhs) -> { @@ -47,8 +66,8 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> { return l.compareTo(r); }); - if (candidates.size() > episodeNumber) { - delete = candidates.subList(0, episodeNumber); + if (candidates.size() > numberOfEpisodesToDelete) { + delete = candidates.subList(0, numberOfEpisodesToDelete); } else { delete = candidates; } @@ -66,34 +85,15 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> { Log.i(TAG, String.format( "Auto-delete deleted %d episodes (%d requested)", counter, - episodeNumber)); + numberOfEpisodesToDelete)); return counter; } @Override - public Integer getDefaultCleanupParameter() { - return getPerformAutoCleanupArgs(0); + public int getDefaultCleanupParameter() { + return getNumEpisodesToCleanup(0); } - @Override - public Integer getPerformCleanupParameter(List<FeedItem> items) { - return getPerformAutoCleanupArgs(items.size()); - } - - static int getPerformAutoCleanupArgs(final int episodeNumber) { - if (episodeNumber >= 0 - && UserPreferences.getEpisodeCacheSize() != UserPreferences - .getEpisodeCacheSizeUnlimited()) { - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(); - if (downloadedEpisodes + episodeNumber >= UserPreferences - .getEpisodeCacheSize()) { - - return downloadedEpisodes + episodeNumber - - UserPreferences.getEpisodeCacheSize(); - } - } - return 0; - } + public int getNumberOfDaysAfterPlayback() { return numberOfDaysAfterPlayback; } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java index f2c56ee79..9e21a55f2 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java @@ -19,8 +19,6 @@ import de.danoeh.antennapod.core.util.PowerUtils; 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 new items and request a download if * 1. Network is available @@ -72,8 +70,8 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm { int autoDownloadableEpisodes = candidates.size(); int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes(); - int deletedEpisodes = cleanupAlgorithm.performCleanup(context, - APCleanupAlgorithm.getPerformAutoCleanupArgs(autoDownloadableEpisodes)); + int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm() + .makeRoomForEpisodes(context, autoDownloadableEpisodes); boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences .getEpisodeCacheSizeUnlimited(); int episodeCacheSize = UserPreferences.getEpisodeCacheSize(); @@ -101,5 +99,4 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm { } }; } - } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java new file mode 100644 index 000000000..132b61853 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java @@ -0,0 +1,24 @@ +package de.danoeh.antennapod.core.storage; + +import android.content.Context; +import android.util.Log; + +/** + * A cleanup algorithm that never removes anything + */ +public class APNullCleanupAlgorithm extends EpisodeCleanupAlgorithm { + + private static final String TAG = "APNullCleanupAlgorithm"; + + @Override + public int performCleanup(Context context, int parameter) { + // never clean anything up + Log.i(TAG, "performCleanup: Not removing anything"); + return 0; + } + + @Override + public int getDefaultCleanupParameter() { + return 0; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java new file mode 100644 index 000000000..234d6162c --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java @@ -0,0 +1,81 @@ +package de.danoeh.antennapod.core.storage; + +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +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.feed.FeedMedia; +import de.danoeh.antennapod.core.util.LongList; + +/** + * A cleanup algorithm that removes any item that isn't in the queue and isn't a favorite + * but only if space is needed. + */ +public class APQueueCleanupAlgorithm extends EpisodeCleanupAlgorithm { + + private static final String TAG = "APQueueCleanupAlgorithm"; + + @Override + public int performCleanup(Context context, int numberOfEpisodesToDelete) { + List<FeedItem> candidates = new ArrayList<>(); + List<FeedItem> downloadedItems = DBReader.getDownloadedItems(); + List<FeedItem> delete; + for (FeedItem item : downloadedItems) { + if (item.hasMedia() + && item.getMedia().isDownloaded() + && !item.isTagged(FeedItem.TAG_QUEUE) + && !item.isTagged(FeedItem.TAG_FAVORITE)) { + candidates.add(item); + } + } + + // in the absence of better data, we'll sort by item publication date + Collections.sort(candidates, (lhs, rhs) -> { + Date l = lhs.getPubDate(); + Date r = rhs.getPubDate(); + + if (l == null) { + l = new Date(); + } + if (r == null) { + r = new Date(); + } + return l.compareTo(r); + }); + + if (candidates.size() > numberOfEpisodesToDelete) { + delete = candidates.subList(0, numberOfEpisodesToDelete); + } 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, + numberOfEpisodesToDelete)); + + return counter; + } + + @Override + public int getDefaultCleanupParameter() { + return getNumEpisodesToCleanup(0); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index 3f9cece77..f54e13471 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -332,9 +332,7 @@ public final class DBTasks { @Override public void run() { ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm() - .performCleanup(context, - ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm() - .getPerformCleanupParameter(Arrays.asList(items))); + .makeRoomForEpisodes(context, items.length); } }.start(); @@ -390,8 +388,7 @@ public final class DBTasks { * @param context Used for accessing the DB. */ public static void performAutoCleanup(final Context context) { - ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context, - ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter()); + ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context); } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java index 91f221f39..0f402745c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java @@ -2,35 +2,60 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; -import java.util.List; +import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.feed.FeedItem; - -public interface EpisodeCleanupAlgorithm<T> { +public abstract class EpisodeCleanupAlgorithm { /** * 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. + * @param context Can be used for accessing the database + * @param numToRemove 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); + public abstract int performCleanup(Context context, int numToRemove); + + public int performCleanup(Context context) { + return performCleanup(context, getDefaultCleanupParameter()); + } /** * 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(); + public abstract int getDefaultCleanupParameter(); /** - * Returns a parameter for performCleanup. + * Cleans up just enough episodes to make room for the requested number * - * @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. + * @param context Can be used for accessing the database + * @param amountOfRoomNeeded the number of episodes we need space for + * @return The number of epiosdes that were deleted + */ + public int makeRoomForEpisodes(Context context, int amountOfRoomNeeded) { + return performCleanup(context, getNumEpisodesToCleanup(amountOfRoomNeeded)); + } + + /** + * @param amountOfRoomNeeded the number of episodes we want to download + * @return the number of episodes to delete in order to make room */ - public T getPerformCleanupParameter(List<FeedItem> items); + protected int getNumEpisodesToCleanup(final int amountOfRoomNeeded) { + if (amountOfRoomNeeded >= 0 + && UserPreferences.getEpisodeCacheSize() != UserPreferences + .getEpisodeCacheSizeUnlimited()) { + int downloadedEpisodes = DBReader + .getNumberOfDownloadedEpisodes(); + if (downloadedEpisodes + amountOfRoomNeeded >= UserPreferences + .getEpisodeCacheSize()) { + + return downloadedEpisodes + amountOfRoomNeeded + - UserPreferences.getEpisodeCacheSize(); + } + } + return 0; + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 9e20693a3..d55d4c231 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -790,6 +790,21 @@ public class PodDBAdapter { db.execSQL(sql); } + public void setFavorites(List<FeedItem> favorites) { + ContentValues values = new ContentValues(); + db.beginTransaction(); + db.delete(TABLE_NAME_FAVORITES, null, null); + for (int i = 0; i < favorites.size(); i++) { + FeedItem item = favorites.get(i); + values.put(KEY_ID, i); + values.put(KEY_FEEDITEM, item.getId()); + values.put(KEY_FEED, item.getFeed().getId()); + db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + /** * Adds the item to favorites */ diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index b2f928617..341a7e520 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -44,6 +44,7 @@ <item>100</item> <item>@string/pref_episode_cache_unlimited</item> </string-array> + <string-array name="episode_cache_size_values"> <item>5</item> <item>10</item> @@ -53,6 +54,26 @@ <item>-1</item> </string-array> + <string-array name="episode_cleanup_entries"> + <item>@string/episode_cleanup_queue_removal</item> + <item>0</item> + <item>1</item> + <item>3</item> + <item>5</item> + <item>7</item> + <item>@string/episode_cleanup_never</item> + </string-array> + + <string-array name="episode_cleanup_values"> + <item>-1</item> + <item>0</item> + <item>1</item> + <item>3</item> + <item>5</item> + <item>7</item> + <item>-2</item> + </string-array> + <string-array name="playback_speed_values"> <item>0.5</item> <item>0.6</item> diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 34f3791c4..168477463 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -87,6 +87,13 @@ <string name="feed_auto_download_always">Always</string> <string name="feed_auto_download_never">Never</string> <string name="send_label">Send...</string> + <string name="episode_cleanup_never">Never</string> + <string name="episode_cleanup_queue_removal">When not in queue</string> + <string name="episode_cleanup_after_listening">After listening</string> + <plurals name="episode_cleanup_days_after_listening"> + <item quantity="one">1 day after listening</item> + <item quantity="other">%d days after listening</item> + </plurals> <!-- 'Add Feed' Activity labels --> <string name="feedurl_label">Feed URL</string> @@ -267,6 +274,8 @@ <string name="queue_label">Queue</string> <string name="services_label">Services</string> <string name="flattr_label">Flattr</string> + <string name="pref_episode_cleanup_title">Episode Cleanup</string> + <string name="pref_episode_cleanup_summary">Episodes that aren\'t in the queue and aren\'t favorites should be eligible for removal if space is needed</string> <string name="pref_pauseOnHeadsetDisconnect_sum">Pause playback when the headphones are disconnected</string> <string name="pref_unpauseOnHeadsetReconnect_sum">Resume playback when the headphones are reconnected</string> <string name="pref_followQueue_sum">Jump to next queue item when playback completes</string> |