Add option to automatically download queue (#7627)

We already added the queue to the auto-download candidates.
Now that auto-download was rewritten to not be a "master switch",
the code is called even if auto-download is turned off for all subscriptions.
This lead to queued episodes being downloaded for users who had auto-download disabled.

Convert the feature to an explicit setting to avoid behavior changes for users.
Also, this implements a setting to auto-download the queue,
which users have requested because they did not know that AntennaPod already does this.
Finally, it should solve user confusion where they automatically add episodes to the queue
but set the auto-download filter to ignore specific episodes.
This commit is contained in:
ByteHamster 2025-02-02 19:28:23 +01:00 committed by GitHub
parent 97ed826687
commit 3ed5b0bfa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 69 additions and 98 deletions

View File

@ -5,6 +5,7 @@ import android.util.Log;
import androidx.annotation.PluralsRes;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
@ -48,14 +49,14 @@ public class EpisodeMultiSelectActionHandler {
}
private void queueChecked(List<FeedItem> items) {
// Check if an episode actually contains any media files before adding it to queue
LongList toQueue = new LongList(items.size());
// Count here to give accurate number in snackbar
List<FeedItem> toQueue = new ArrayList<>();
for (FeedItem episode : items) {
if (episode.hasMedia()) {
toQueue.add(episode.getId());
if (episode.hasMedia() && !episode.isTagged(FeedItem.TAG_QUEUE)) {
toQueue.add(episode);
}
}
DBWriter.addQueueItem(activity, true, toQueue.toArray());
DBWriter.addQueueItem(activity, toQueue.toArray(new FeedItem[0]));
showMessage(R.plurals.added_to_queue_batch_label, toQueue.size());
}

View File

@ -46,7 +46,7 @@ public class RemoveFromQueueSwipeAction implements SwipeAction {
fragment.getResources().getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1),
Snackbar.LENGTH_LONG)
.setAction(fragment.getString(R.string.undo), v ->
DBWriter.addQueueItemAt(fragment.requireActivity(), item.getId(), position, false));
DBWriter.addQueueItemAt(fragment.requireActivity(), item.getId(), position));
}
}

View File

@ -50,12 +50,9 @@ public class AutomaticDownloadAlgorithm {
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
List<FeedItem> candidates;
final List<FeedItem> queue = DBReader.getQueue();
final List<FeedItem> newItems = DBReader.getEpisodes(0, Integer.MAX_VALUE,
new FeedItemFilter(FeedItemFilter.NEW), SortOrder.DATE_NEW_OLD);
candidates = new ArrayList<>(queue.size() + newItems.size());
candidates.addAll(queue);
final List<FeedItem> candidates = new ArrayList<>();
for (FeedItem newItem : newItems) {
FeedPreferences feedPrefs = newItem.getFeed().getPreferences();
if (feedPrefs.isAutoDownload(UserPreferences.isEnableAutodownloadGlobal())
@ -65,6 +62,15 @@ public class AutomaticDownloadAlgorithm {
}
}
if (UserPreferences.isEnableAutodownloadQueue()) {
final List<FeedItem> queue = DBReader.getQueue();
for (FeedItem item : queue) {
if (!candidates.contains(item)) {
candidates.add(item);
}
}
}
// filter items that are not auto downloadable
Iterator<FeedItem> it = candidates.iterator();
while (it.hasNext()) {

View File

@ -51,7 +51,7 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface {
.addTag(DownloadServiceInterface.WORK_TAG)
.addTag(DownloadServiceInterface.WORK_TAG_EPISODE_URL + item.getMedia().getDownloadUrl());
if (!item.isTagged(FeedItem.TAG_QUEUE) && UserPreferences.enqueueDownloadedEpisodes()) {
DBWriter.addQueueItem(context, false, item.getId());
DBWriter.addQueueItem(context, item);
workRequest.addTag(DownloadServiceInterface.WORK_DATA_WAS_QUEUED);
}
workRequest.setInputData(new Data.Builder().putLong(WORK_DATA_MEDIA_ID, item.getMedia().getId()).build());

View File

@ -1801,8 +1801,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private void addPlayableToQueue(Playable playable) {
if (playable instanceof FeedMedia) {
long itemId = ((FeedMedia) playable).getItem().getId();
DBWriter.addQueueItem(this, false, true, itemId);
DBWriter.addQueueItem(this, ((FeedMedia) playable).getItem());
notifyChildrenChanged(getString(R.string.queue_label));
}
}

View File

@ -334,20 +334,16 @@ public class DBWriter {
* @param context A context that is used for opening a database connection.
* @param itemId ID of the FeedItem that should be added to the queue.
* @param index Destination index. Must be in range 0..queue.size()
* @param performAutoDownload True if an auto-download process should be started after the operation
* @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
*/
public static Future<?> addQueueItemAt(final Context context, final long itemId,
final int index, final boolean performAutoDownload) {
public static Future<?> addQueueItemAt(final Context context, final long itemId, final int index) {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final List<FeedItem> queue = DBReader.getQueue();
FeedItem item;
if (queue != null) {
if (!itemListContains(queue, itemId)) {
item = DBReader.getFeedItem(itemId);
FeedItem item = DBReader.getFeedItem(itemId);
if (item != null) {
queue.add(index, item);
adapter.setQueue(queue);
@ -359,59 +355,22 @@ public class DBWriter {
}
}
}
}
adapter.close();
if (performAutoDownload) {
AutoDownloadManager.getInstance().autodownloadUndownloadedItems(context);
}
});
}
/**
* Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
* If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
*
* @param context A context that is used for opening a database connection.
* @param items FeedItem objects that should be added to the queue.
*/
public static Future<?> addQueueItem(final Context context, final FeedItem... items) {
return addQueueItem(context, true, items);
}
public static Future<?> addQueueItem(final Context context, boolean markAsUnplayed, final FeedItem... items) {
LongList itemIds = new LongList(items.length);
for (FeedItem item : items) {
if (!item.hasMedia()) {
continue;
}
itemIds.add(item.getId());
item.addTag(FeedItem.TAG_QUEUE);
}
return addQueueItem(context, false, markAsUnplayed, itemIds.toArray());
}
/**
* Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
* If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
*
* @param context A context that is used for opening a database connection.
* @param performAutoDownload true if an auto-download process should be started after the operation.
* @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/
public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final long... itemIds) {
return addQueueItem(context, performAutoDownload, true, itemIds);
}
/**
* Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
* If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
*
* @param context A context that is used for opening a database connection.
* @param performAutoDownload true if an auto-download process should be started after the operation.
* @param markAsUnplayed true if the items should be marked as unplayed when enqueueing
* @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/
public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final boolean markAsUnplayed, final long... itemIds) {
return runOnDbThread(() -> {
if (itemIds.length < 1) {
if (items.length < 1) {
return;
}
@ -419,7 +378,6 @@ public class DBWriter {
adapter.open();
final List<FeedItem> queue = DBReader.getQueue();
boolean queueModified = false;
LongList markAsUnplayedIds = new LongList();
List<QueueEvent> events = new ArrayList<>();
List<FeedItem> updatedItems = new ArrayList<>();
@ -427,38 +385,35 @@ public class DBWriter {
new ItemEnqueuePositionCalculator(UserPreferences.getEnqueueLocation());
Playable currentlyPlaying = DBReader.getFeedMedia(PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
int insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying);
for (long itemId : itemIds) {
if (!itemListContains(queue, itemId)) {
final FeedItem item = DBReader.getFeedItem(itemId);
if (item != null) {
for (FeedItem item : items) {
if (itemListContains(queue, item.getId())) {
continue;
} else if (!item.hasMedia()) {
continue;
}
queue.add(insertPosition, item);
events.add(QueueEvent.added(item, insertPosition));
item.addTag(FeedItem.TAG_QUEUE);
updatedItems.add(item);
queueModified = true;
if (item.isNew()) {
markAsUnplayedIds.add(item.getId());
}
insertPosition++;
}
}
}
if (queueModified) {
if (!updatedItems.isEmpty()) {
applySortOrder(queue, events);
adapter.setQueue(queue);
for (QueueEvent event : events) {
EventBus.getDefault().post(event);
}
EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
if (markAsUnplayed && markAsUnplayedIds.size() > 0) {
if (markAsUnplayedIds.size() > 0) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
}
}
adapter.close();
if (performAutoDownload) {
AutoDownloadManager.getInstance().autodownloadUndownloadedItems(context);
}
});
}

View File

@ -13,9 +13,7 @@ import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
import de.danoeh.antennapod.model.playback.Playable;
/**
* @see de.danoeh.antennapod.storage.database.DBWriter#addQueueItem(android.content.Context, boolean, long...)
* it uses the class to determine
* the positions of the {@link FeedItem} in the queue.
* Determine the positions of the new {@link FeedItem} in the queue.
*/
public class ItemEnqueuePositionCalculator {

View File

@ -99,6 +99,7 @@ public abstract class UserPreferences {
public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
public static final String PREF_AUTODL_GLOBAL = "prefEnableAutoDl";
public static final String PREF_AUTODL_QUEUE = "prefEnableAutoDlQueue";
public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
private static final String PREF_PROXY_TYPE = "prefProxyType";
private static final String PREF_PROXY_HOST = "prefProxyHost";
@ -539,6 +540,10 @@ public abstract class UserPreferences {
return prefs.getBoolean(PREF_AUTODL_GLOBAL, false);
}
public static boolean isEnableAutodownloadQueue() {
return prefs.getBoolean(PREF_AUTODL_QUEUE, false);
}
public static boolean isEnableAutodownloadOnBattery() {
return prefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
}

View File

@ -481,6 +481,8 @@
<string name="pref_nav_drawer_feed_counter_sum">Change the information displayed by the subscription counter. Also affects the sorting of subscriptions if \'Subscription Order\' is set to \'Counter\'.</string>
<string name="pref_automatic_download_title">Automatic download</string>
<string name="pref_automatic_download_global_description">Automatically download new episodes. Can be overridden per podcast.</string>
<string name="pref_automatic_download_queue_title">Download queued</string>
<string name="pref_automatic_download_queue_description">Automatically download queued episodes</string>
<string name="pref_automatic_download_sum">Configure the automatic download of episodes</string>
<string name="pref_automatic_download_on_battery_title">Download when not charging</string>
<string name="pref_automatic_download_on_battery_sum">Allow automatic download when the battery is not charging</string>

View File

@ -6,6 +6,11 @@
android:key="prefEnableAutoDl"
android:title="@string/pref_automatic_download_title"
android:summary="@string/pref_automatic_download_global_description"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="prefEnableAutoDlQueue"
android:title="@string/pref_automatic_download_queue_title"
android:summary="@string/pref_automatic_download_queue_description"/>
<de.danoeh.antennapod.ui.preferences.preference.MaterialListPreference
android:defaultValue="25"
android:entries="@array/episode_cache_size_entries"