Refactoring: Remove ClientConfig.automaticDownloadAlgorithm (#4924)

This commit is contained in:
Herbert Reiter 2021-02-07 17:57:09 +01:00 committed by GitHub
parent ded779d0c9
commit 60968089ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 133 deletions

View File

@ -3,13 +3,13 @@ package de.test.antennapod.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.ui.UITestUtils;
@ -29,8 +29,7 @@ public class AutoDownloadTest {
private Context context;
private UITestUtils stubFeedsServer;
private AutomaticDownloadAlgorithm automaticDownloadAlgorithmOrig;
private StubDownloadAlgorithm stubDownloadAlgorithm;
@Before
public void setUp() throws Exception {
@ -39,16 +38,19 @@ public class AutoDownloadTest {
stubFeedsServer = new UITestUtils(context);
stubFeedsServer.setup();
automaticDownloadAlgorithmOrig = ClientConfig.automaticDownloadAlgorithm;
EspressoTestUtils.clearPreferences();
EspressoTestUtils.clearDatabase();
UserPreferences.setAllowMobileStreaming(true);
// Setup: enable automatic download
// it is not needed, as the actual automatic download is stubbed.
stubDownloadAlgorithm = new StubDownloadAlgorithm();
DBTasks.setDownloadAlgorithm(stubDownloadAlgorithm);
}
@After
public void tearDown() throws Exception {
ClientConfig.automaticDownloadAlgorithm = automaticDownloadAlgorithmOrig;
DBTasks.setDownloadAlgorithm(new AutomaticDownloadAlgorithm());
EspressoTestUtils.tryKillPlaybackService();
stubFeedsServer.tearDown();
}
@ -74,11 +76,6 @@ public class AutoDownloadTest {
FeedItem item0 = queue.get(0);
FeedItem item1 = queue.get(1);
// Setup: enable automatic download
// it is not needed, as the actual automatic download is stubbed.
StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm();
ClientConfig.automaticDownloadAlgorithm = stubDownloadAlgorithm;
// Actual test
// Play the first one in the queue
playEpisode(item0);
@ -92,11 +89,10 @@ public class AutoDownloadTest {
} catch (ConditionTimeoutException cte) {
long actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload();
fail("when auto download is triggered, the next episode should be playing: ("
+ item1.getId() + ", " + item1.getTitle() + ") . "
+ item1.getId() + ", " + item1.getTitle() + ") . "
+ "Actual playing: (" + actual + ")"
);
}
}
private void playEpisode(@NonNull FeedItem item) {
@ -111,7 +107,7 @@ public class AutoDownloadTest {
.until(() -> item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
}
private static class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm {
private static class StubDownloadAlgorithm extends AutomaticDownloadAlgorithm {
private long currentlyPlaying = -1;
@Override

View File

@ -2,21 +2,20 @@ package de.danoeh.antennapod.config;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
/**
* Configures the ClientConfig class of the core package.
*/
class ClientConfigurator {
private ClientConfigurator(){}
private ClientConfigurator() {
}
static {
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm();
ClientConfig.castCallbacks = new CastCallbackImpl();
}
}

View File

@ -9,7 +9,6 @@ import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UsageStatistics;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
@ -33,8 +32,6 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
public static AutomaticDownloadAlgorithm automaticDownloadAlgorithm;
public static CastCallbacks castCallbacks;
private static boolean initialized = false;

View File

@ -1,103 +0,0 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedFilter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedPreferences;
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";
/**
* Looks for undownloaded episodes in the queue or list of new 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.
* @return A Runnable that will be submitted to an ExecutorService.
*/
@Override
public Runnable autoDownloadUndownloadedItems(final Context context) {
return () -> {
// true if we should auto download based on network status
boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable()
&& 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");
List<FeedItem> candidates;
final List<FeedItem> queue = DBReader.getQueue();
final List<FeedItem> newItems = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
candidates = new ArrayList<>(queue.size() + newItems.size());
candidates.addAll(queue);
for (FeedItem newItem : newItems) {
FeedPreferences feedPrefs = newItem.getFeed().getPreferences();
FeedFilter feedFilter = feedPrefs.getFilter();
if (!candidates.contains(newItem) && feedFilter.shouldAutoDownload(newItem)) {
candidates.add(newItem);
}
}
// filter items that are not auto downloadable
Iterator<FeedItem> it = candidates.iterator();
while (it.hasNext()) {
FeedItem item = it.next();
if (!item.isAutoDownloadable() || item.getFeed().isLocalFeed()) {
it.remove();
}
}
int autoDownloadableEpisodes = candidates.size();
int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
.makeRoomForEpisodes(context, autoDownloadableEpisodes);
boolean cacheIsUnlimited =
UserPreferences.getEpisodeCacheSize() == UserPreferences.getEpisodeCacheSizeUnlimited();
int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
int episodeSpaceLeft;
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
episodeSpaceLeft = autoDownloadableEpisodes;
} else {
episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
}
FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
.toArray(new FeedItem[episodeSpaceLeft]);
if (itemsToDownload.length > 0) {
Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
try {
DownloadRequester.getInstance().downloadMedia(false, context, false, itemsToDownload);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
};
}
}

View File

@ -1,11 +1,28 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
public interface AutomaticDownloadAlgorithm {
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedFilter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedPreferences;
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 {@link EpisodeCleanupAlgorithm}.
*/
public class AutomaticDownloadAlgorithm {
private static final String TAG = "DownloadAlgorithm";
/**
* Looks for undownloaded episodes and request a download if
* Looks for undownloaded episodes in the queue or list of new 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
@ -14,5 +31,72 @@ public interface AutomaticDownloadAlgorithm {
* @param context Used for accessing the DB.
* @return A Runnable that will be submitted to an ExecutorService.
*/
Runnable autoDownloadUndownloadedItems(Context context);
public Runnable autoDownloadUndownloadedItems(final Context context) {
return () -> {
// true if we should auto download based on network status
boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable()
&& 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");
List<FeedItem> candidates;
final List<FeedItem> queue = DBReader.getQueue();
final List<FeedItem> newItems = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
candidates = new ArrayList<>(queue.size() + newItems.size());
candidates.addAll(queue);
for (FeedItem newItem : newItems) {
FeedPreferences feedPrefs = newItem.getFeed().getPreferences();
FeedFilter feedFilter = feedPrefs.getFilter();
if (!candidates.contains(newItem) && feedFilter.shouldAutoDownload(newItem)) {
candidates.add(newItem);
}
}
// filter items that are not auto downloadable
Iterator<FeedItem> it = candidates.iterator();
while (it.hasNext()) {
FeedItem item = it.next();
if (!item.isAutoDownloadable() || item.getFeed().isLocalFeed()) {
it.remove();
}
}
int autoDownloadableEpisodes = candidates.size();
int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
.makeRoomForEpisodes(context, autoDownloadableEpisodes);
boolean cacheIsUnlimited =
UserPreferences.getEpisodeCacheSize() == UserPreferences.getEpisodeCacheSizeUnlimited();
int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
int episodeSpaceLeft;
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
episodeSpaceLeft = autoDownloadableEpisodes;
} else {
episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
}
FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
.toArray(new FeedItem[episodeSpaceLeft]);
if (itemsToDownload.length > 0) {
Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
try {
DownloadRequester.getInstance().downloadMedia(false, context, false, itemsToDownload);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
};
}
}

View File

@ -6,7 +6,9 @@ import android.database.Cursor;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.core.ClientConfig;
import androidx.annotation.VisibleForTesting;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
@ -53,6 +55,8 @@ public final class DBTasks {
*/
private static final ExecutorService autodownloadExec;
private static AutomaticDownloadAlgorithm downloadAlgorithm = new AutomaticDownloadAlgorithm();
static {
autodownloadExec = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
@ -278,7 +282,7 @@ public final class DBTasks {
}
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* Looks for non-downloaded 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
@ -289,9 +293,15 @@ public final class DBTasks {
*/
public static Future<?> autodownloadUndownloadedItems(final Context context) {
Log.d(TAG, "autodownloadUndownloadedItems");
return autodownloadExec.submit(ClientConfig.automaticDownloadAlgorithm
.autoDownloadUndownloadedItems(context));
return autodownloadExec.submit(downloadAlgorithm.autoDownloadUndownloadedItems(context));
}
/**
* For testing purpose only.
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public static void setDownloadAlgorithm(AutomaticDownloadAlgorithm newDownloadAlgorithm) {
downloadAlgorithm = newDownloadAlgorithm;
}
/**

View File

@ -12,7 +12,6 @@ import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UsageStatistics;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
@ -39,8 +38,6 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
public static AutomaticDownloadAlgorithm automaticDownloadAlgorithm;
public static CastCallbacks castCallbacks;
private static boolean initialized = false;