Added new cleanup option: when not favorited

This is another way of solving #2077.

The root issue is that queued episodes are never auto-deleted
currently which means that if you automatically add episodes to the
queue you will eventually end up with AntennaPod refusing to auto
download more episodes because the cache is full and it can't make
space.

This option will only refuse to delete favorited items. Otherwise it
will simply delete the oldest episodes.
This commit is contained in:
Jonas Kalderstam 2020-11-16 11:29:11 +01:00
parent 9924952e2f
commit 08edd151f9
7 changed files with 206 additions and 3 deletions

View File

@ -15,6 +15,7 @@ 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;
import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.fragment.SubscriptionFragment;
@ -371,6 +372,17 @@ public class PreferencesTest {
.until(() -> enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery());
}
@Test
public void testEpisodeCleanupFavoriteOnly() {
clickPreference(R.string.network_pref);
onView(withText(R.string.pref_automatic_download_title)).perform(click());
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
onView(isRoot()).perform(waitForView(withText(R.string.episode_cleanup_except_favorite_removal), 1000));
onView(withText(R.string.episode_cleanup_except_favorite_removal)).perform(click());
Awaitility.await().atMost(1000, MILLISECONDS)
.until(() -> UserPreferences.getEpisodeCleanupAlgorithm() instanceof ExceptFavoriteCleanupAlgorithm);
}
@Test
public void testEpisodeCleanupQueueOnly() {
clickPreference(R.string.network_pref);

View File

@ -174,7 +174,9 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat {
String[] entries = new String[values.length];
for (int x = 0; x < values.length; x++) {
int v = Integer.parseInt(values[x]);
if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
if (v == UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE) {
entries[x] = res.getString(R.string.episode_cleanup_except_favorite_removal);
} else if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
entries[x] = res.getString(R.string.episode_cleanup_queue_removal);
} else if (v == UserPreferences.EPISODE_CLEANUP_NULL){
entries[x] = res.getString(R.string.episode_cleanup_never);

View File

@ -36,6 +36,7 @@ import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
import de.danoeh.antennapod.core.service.download.ProxyConfig;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
@ -137,6 +138,7 @@ public class UserPreferences {
public static final String PREF_CAST_ENABLED = "prefCast"; //Used for enabling Chromecast support
public static final int EPISODE_CLEANUP_QUEUE = -1;
public static final int EPISODE_CLEANUP_NULL = -2;
public static final int EPISODE_CLEANUP_EXCEPT_FAVORITE = -3;
public static final int EPISODE_CLEANUP_DEFAULT = 0;
// Constants
@ -882,7 +884,9 @@ public class UserPreferences {
return new APNullCleanupAlgorithm();
}
int cleanupValue = getEpisodeCleanupValue();
if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
if (cleanupValue == EPISODE_CLEANUP_EXCEPT_FAVORITE) {
return new ExceptFavoriteCleanupAlgorithm();
} else if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
return new APQueueCleanupAlgorithm();
} else if (cleanupValue == EPISODE_CLEANUP_NULL) {
return new APNullCleanupAlgorithm();

View File

@ -0,0 +1,93 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.feed.FeedItem;
/**
* A cleanup algorithm that removes any item that isn't a favorite but only if space is needed.
*/
public class ExceptFavoriteCleanupAlgorithm extends EpisodeCleanupAlgorithm {
private static final String TAG = "ExceptFavCleanupAlgo";
/**
* The maximum number of episodes that could be cleaned up.
*
* @return the number of episodes that *could* be cleaned up, if needed
*/
public int getReclaimableItems() {
return getCandidates().size();
}
@Override
public int performCleanup(Context context, int numberOfEpisodesToDelete) {
List<FeedItem> candidates = getCandidates();
List<FeedItem> delete;
// 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 && r != null) {
return l.compareTo(r);
} else {
// No date - compare by id which should be always incremented
return Long.compare(lhs.getId(), rhs.getId());
}
});
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(Locale.US,
"Auto-delete deleted %d episodes (%d requested)", counter,
numberOfEpisodesToDelete));
return counter;
}
@NonNull
private List<FeedItem> getCandidates() {
List<FeedItem> candidates = new ArrayList<>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
for (FeedItem item : downloadedItems) {
if (item.hasMedia()
&& item.getMedia().isDownloaded()
&& !item.isTagged(FeedItem.TAG_FAVORITE)) {
candidates.add(item);
}
}
return candidates;
}
@Override
public int getDefaultCleanupParameter() {
return getNumEpisodesToCleanup(0);
}
}

View File

@ -96,6 +96,7 @@
</string-array>
<string-array name="episode_cleanup_entries">
<item>@string/episode_cleanup_except_favorite_removal</item>
<item>@string/episode_cleanup_queue_removal</item>
<item>0</item>
<item>1</item>
@ -133,6 +134,7 @@
</string-array>
<string-array name="episode_cleanup_values">
<item>-3</item>
<item>-1</item>
<item>0</item>
<item>12</item>

View File

@ -116,6 +116,7 @@
<string name="feed_auto_download_never">Never</string>
<string name="send_label">Send&#8230;</string>
<string name="episode_cleanup_never">Never</string>
<string name="episode_cleanup_except_favorite_removal">When not favorited</string>
<string name="episode_cleanup_queue_removal">When not in queue</string>
<string name="episode_cleanup_after_listening">After finishing</string>
<plurals name="episode_cleanup_hours_after_listening">
@ -387,7 +388,7 @@
<string name="preference_search_clear_history">Clear history</string>
<string name="media_player">Media player</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 Auto Download needs space for new episodes</string>
<string name="pref_episode_cleanup_summary">Episodes that should be eligible for removal if Auto Download needs space for new episodes</string>
<string name="pref_pauseOnDisconnect_sum">Pause playback when headphones or bluetooth are disconnected</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Resume playback when the headphones are reconnected</string>
<string name="pref_unpauseOnBluetoothReconnect_sum">Resume playback when bluetooth reconnects</string>

View File

@ -0,0 +1,89 @@
package de.danoeh.antennapod.core.storage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests that the APFavoriteCleanupAlgorithm is working correctly.
*/
@RunWith(RobolectricTestRunner.class)
public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests {
private final int numberOfItems = EPISODE_CACHE_SIZE * 2;
public ExceptFavoriteCleanupAlgorithmTest() {
setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE);
}
@Test
public void testPerformAutoCleanupHandleUnplayed() throws IOException {
Feed feed = new Feed("url", null, "title");
List<FeedItem> items = new ArrayList<>();
feed.setItems(items);
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, false);
DBTasks.performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue("Only enough items should be deleted", files.get(i).exists());
} else {
assertFalse("Expected episode to be deleted", files.get(i).exists());
}
}
}
@Test
public void testPerformAutoCleanupDeletesQueued() throws IOException {
Feed feed = new Feed("url", null, "title");
List<FeedItem> items = new ArrayList<>();
feed.setItems(items);
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, true, false);
DBTasks.performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue("Only enough items should be deleted", files.get(i).exists());
} else {
assertFalse("Queued episodes should be deleted", files.get(i).exists());
}
}
}
@Test
public void testPerformAutoCleanupSavesFavorited() throws IOException {
Feed feed = new Feed("url", null, "title");
List<FeedItem> items = new ArrayList<>();
feed.setItems(items);
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, true);
DBTasks.performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
assertTrue("Favorite episodes should should not be deleted", files.get(i).exists());
}
}
@Override
public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
// Yes it should
}
@Override
public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException {
// Yes it should
}
}