Delete local feed episodes (#6400)

This commit is contained in:
Matej Drobnič 2023-10-22 16:53:41 +02:00 committed by GitHub
parent fa75317bce
commit 346365b8d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 225 additions and 68 deletions

View File

@ -41,6 +41,7 @@ import static de.test.antennapod.EspressoTestUtils.clickPreference;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@LargeTest
@ -199,6 +200,22 @@ public class PreferencesTest {
.until(() -> autoDelete == UserPreferences.isAutoDelete());
}
@Test
public void testAutoDeleteLocal() {
clickPreference(R.string.downloads_pref);
final boolean initialAutoDelete = UserPreferences.isAutoDeleteLocal();
assertFalse(initialAutoDelete);
onView(withText(R.string.pref_auto_local_delete_title)).perform(click());
onView(withText(R.string.yes)).perform(click());
Awaitility.await().atMost(1000, MILLISECONDS)
.until(() -> UserPreferences.isAutoDeleteLocal());
onView(withText(R.string.pref_auto_local_delete_title)).perform(click());
Awaitility.await().atMost(1000, MILLISECONDS)
.until(() -> !UserPreferences.isAutoDeleteLocal());
}
@Test
public void testPlaybackSpeeds() {
clickPreference(R.string.playback_pref);

View File

@ -4,10 +4,14 @@ import android.content.Context;
import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import java.util.Collections;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.view.LocalDeleteModal;
public class DeleteActionButton extends ItemActionButton {
@ -33,11 +37,17 @@ public class DeleteActionButton extends ItemActionButton {
if (media == null) {
return;
}
DBWriter.deleteFeedMediaOfItem(context, media.getId());
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(context, Collections.singletonList(item),
() -> DBWriter.deleteFeedMediaOfItem(context, media.getId()));
}
@Override
public int getVisibility() {
return (item.getMedia() != null && item.getMedia().isDownloaded()) ? View.VISIBLE : View.INVISIBLE;
if (item.getMedia() != null && (item.getMedia().isDownloaded() || item.getFeed().isLocalFeed())) {
return View.VISIBLE;
}
return View.INVISIBLE;
}
}

View File

@ -197,8 +197,8 @@ public class AddFeedFragment extends Fragment {
}
private Feed addLocalFolder(Uri uri) {
getActivity().getContentResolver()
.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
getActivity().getContentResolver().takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
DocumentFile documentFile = DocumentFile.fromTreeUri(getContext(), uri);
if (documentFile == null) {
throw new IllegalArgumentException("Unable to retrieve document tree");

View File

@ -367,7 +367,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
swipeActions.detach();
if (feed.isLocalFeed()) {
speedDialBinding.fabSD.removeActionItemById(R.id.download_batch);
speedDialBinding.fabSD.removeActionItemById(R.id.delete_batch);
}
speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item);
speedDialBinding.fabSD.setVisibility(View.VISIBLE);

View File

@ -172,7 +172,6 @@ public class FeedSettingsFragment extends Fragment {
if (feed.isLocalFeed()) {
findPreference(PREF_AUTHENTICATION).setVisible(false);
findPreference(PREF_AUTO_DELETE).setVisible(false);
findPreference(PREF_CATEGORY_AUTO_DOWNLOAD).setVisible(false);
}

View File

@ -14,6 +14,7 @@ import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterfa
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.view.LocalDeleteModal;
public class EpisodeMultiSelectActionHandler {
private static final String TAG = "EpisodeSelectHandler";
@ -41,7 +42,7 @@ public class EpisodeMultiSelectActionHandler {
} else if (actionId == R.id.download_batch) {
downloadChecked(items);
} else if (actionId == R.id.delete_batch) {
deleteChecked(items);
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(activity, items, () -> deleteChecked(items));
} else {
Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + actionId);
}

View File

@ -2,8 +2,12 @@ package de.danoeh.antennapod.fragment.preferences;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.TwoStatePreference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
@ -17,9 +21,12 @@ import java.io.File;
public class DownloadsPreferencesFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String PREF_SCREEN_AUTODL = "prefAutoDownloadSettings";
private static final String PREF_AUTO_DELETE_LOCAL = "prefAutoDeleteLocal";
private static final String PREF_PROXY = "prefProxy";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private boolean blockAutoDeleteLocal = true;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_downloads);
@ -63,6 +70,14 @@ public class DownloadsPreferencesFragment extends PreferenceFragmentCompat
});
return true;
});
findPreference(PREF_AUTO_DELETE_LOCAL).setOnPreferenceChangeListener((preference, newValue) -> {
if (blockAutoDeleteLocal && newValue == Boolean.TRUE) {
showAutoDeleteEnableDialog();
return false;
} else {
return true;
}
});
}
private void setDataFolderText() {
@ -78,4 +93,16 @@ public class DownloadsPreferencesFragment extends PreferenceFragmentCompat
FeedUpdateManager.restartUpdateAlarm(getContext(), true);
}
}
private void showAutoDeleteEnableDialog() {
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.pref_auto_local_delete_dialog_body)
.setPositiveButton(R.string.yes, (dialog, which) -> {
blockAutoDeleteLocal = false;
((TwoStatePreference) findPreference(PREF_AUTO_DELETE_LOCAL)).setChecked(true);
blockAutoDeleteLocal = true;
})
.setNegativeButton(R.string.cancel_label, null)
.show();
}
}

View File

@ -2,10 +2,14 @@ package de.danoeh.antennapod.fragment.swipeactions;
import android.content.Context;
import androidx.fragment.app.Fragment;
import java.util.Collections;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.view.LocalDeleteModal;
public class DeleteSwipeAction implements SwipeAction {
@ -31,14 +35,16 @@ public class DeleteSwipeAction implements SwipeAction {
@Override
public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
if (!item.isDownloaded()) {
if (!item.isDownloaded() && !item.getFeed().isLocalFeed()) {
return;
}
DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), item.getMedia().getId());
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(
fragment.requireContext(), Collections.singletonList(item),
() -> DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), item.getMedia().getId()));
}
@Override
public boolean willRemove(FeedItemFilter filter, FeedItem item) {
return filter.showDownloaded && item.isDownloaded();
return filter.showDownloaded && (item.isDownloaded() || item.getFeed().isLocalFeed());
}
}

View File

@ -12,10 +12,12 @@ import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.FeedUtil;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface;
import de.danoeh.antennapod.core.storage.DBWriter;
@ -29,6 +31,7 @@ import de.danoeh.antennapod.dialog.ShareDialog;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.view.LocalDeleteModal;
/**
* Handles interactions with the FeedItemMenu.
@ -56,6 +59,7 @@ public class FeedItemMenuHandler {
final boolean isPlaying = hasMedia && PlaybackStatus.isPlaying(selectedItem.getMedia());
final boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE);
final boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
final boolean isLocalFile = hasMedia && selectedItem.getFeed().isLocalFeed();
final boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
setItemVisibility(menu, R.id.skip_episode_item, isPlaying);
@ -80,7 +84,7 @@ public class FeedItemMenuHandler {
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite);
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite);
setItemVisibility(menu, R.id.remove_item, fileDownloaded);
setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile);
return true;
}
@ -148,7 +152,8 @@ public class FeedItemMenuHandler {
if (menuItemId == R.id.skip_episode_item) {
context.sendBroadcast(MediaButtonReceiver.createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT));
} else if (menuItemId == R.id.remove_item) {
DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(context, Arrays.asList(selectedItem),
() -> DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()));
} else if (menuItemId == R.id.remove_inbox_item) {
removeNewFlagWithUndo(fragment, selectedItem);
} else if (menuItemId == R.id.mark_read_item) {
@ -225,7 +230,8 @@ public class FeedItemMenuHandler {
final Handler h = new Handler(fragment.requireContext().getMainLooper());
final Runnable r = () -> {
FeedMedia media = item.getMedia();
if (media != null && FeedItemUtil.hasAlmostEnded(media) && UserPreferences.isAutoDelete()) {
boolean shouldAutoDelete = FeedUtil.shouldAutoDeleteItemsOnThatFeed(item.getFeed());
if (media != null && FeedItemUtil.hasAlmostEnded(media) && shouldAutoDelete) {
DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), media.getId());
}
};

View File

@ -0,0 +1,32 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.ui.i18n.R;
import de.danoeh.antennapod.model.feed.FeedItem;
public class LocalDeleteModal {
public static void showLocalFeedDeleteWarningIfNecessary(Context context, Iterable<FeedItem> items,
Runnable deleteCommand) {
boolean anyLocalFeed = false;
for (FeedItem item : items) {
if (item.getFeed().isLocalFeed()) {
anyLocalFeed = true;
break;
}
}
if (!anyLocalFeed) {
deleteCommand.run();
return;
}
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_episode_label)
.setMessage(R.string.delete_local_feed_warning_body)
.setPositiveButton(R.string.delete_label, (dialog, which) -> deleteCommand.run())
.setNegativeButton(R.string.cancel_label, null)
.show();
}
}

View File

@ -33,6 +33,12 @@
android:key="prefAutoDelete"
android:summary="@string/pref_auto_delete_sum"
android:title="@string/pref_auto_delete_title"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:enabled="true"
android:key="prefAutoDeleteLocal"
android:summary="@string/pref_auto_local_delete_sum"
android:title="@string/pref_auto_local_delete_title"/>
<SwitchPreferenceCompat
android:defaultValue="true"
android:enabled="true"

View File

@ -37,7 +37,6 @@ import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.playback.MediaType;
import de.danoeh.antennapod.parser.feed.util.MimeTypeUtils;
import de.danoeh.antennapod.parser.media.id3.ID3ReaderException;
@ -124,13 +123,9 @@ public class LocalFeedUpdater {
feed.setImageUrl(getImageUrl(allFiles, folderUri));
feed.getPreferences().setAutoDownload(false);
feed.getPreferences().setAutoDeleteAction(FeedPreferences.AutoDeleteAction.NEVER);
feed.setDescription(context.getString(R.string.local_feed_description));
feed.setAuthor(context.getString(R.string.local_folder));
if (newItems.isEmpty()) {
throw new IOException("Empty folder. Make sure that the folder is accessible and contains media files.");
}
DBTasks.updateFeed(context, feed, true);
}

View File

@ -68,6 +68,7 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.FeedUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
@ -1103,7 +1104,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
FeedPreferences.AutoDeleteAction action =
item.getFeed().getPreferences().getCurrentAutoDelete();
boolean shouldAutoDelete = action == FeedPreferences.AutoDeleteAction.ALWAYS
|| (action == FeedPreferences.AutoDeleteAction.GLOBAL && UserPreferences.isAutoDelete());
|| (action == FeedPreferences.AutoDeleteAction.GLOBAL
&& FeedUtil.shouldAutoDeleteItemsOnThatFeed(item.getFeed()));
if (shouldAutoDelete && (!item.isTagged(FeedItem.TAG_FAVORITE)
|| !UserPreferences.shouldFavoriteKeepEpisode())) {
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());

View File

@ -2,15 +2,22 @@ package de.danoeh.antennapod.core.storage;
import android.app.backup.BackupManager;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationManagerCompat;
import androidx.documentfile.provider.DocumentFile;
import com.google.common.util.concurrent.Futures;
import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
@ -25,7 +32,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.event.FavoritesEvent;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
@ -94,7 +100,7 @@ public class DBWriter {
*/
public static Future<?> deleteFeedMediaOfItem(@NonNull final Context context,
final long mediaId) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final FeedMedia media = DBReader.getFeedMedia(mediaId);
if (media != null) {
boolean result = deleteFeedMediaSynchronous(context, media);
@ -106,10 +112,10 @@ public class DBWriter {
});
}
private static boolean deleteFeedMediaSynchronous(
@NonNull Context context, @NonNull FeedMedia media) {
private static boolean deleteFeedMediaSynchronous(@NonNull Context context, @NonNull FeedMedia media) {
Log.i(TAG, String.format(Locale.US, "Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s",
media.getId(), media.getEpisodeTitle(), media.isDownloaded()));
boolean localDelete = false;
if (media.isDownloaded()) {
// delete downloaded media file
File mediaFile = new File(media.getFile_url());
@ -125,23 +131,38 @@ public class DBWriter {
adapter.open();
adapter.setMedia(media);
adapter.close();
if (media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId()) {
PlaybackPreferences.writeNoMediaPlaying();
IntentUtils.sendLocalBroadcast(context, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.cancel(R.id.notification_playing);
} else if (media.getFile_url().startsWith("content://")) {
// Local feed
DocumentFile documentFile = DocumentFile.fromSingleUri(
context, Uri.parse(media.getFile_url()));
if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
EventBus.getDefault().post(new MessageEvent(context.getString(R.string.delete_local_failed)));
return false;
}
localDelete = true;
}
if (media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId()) {
PlaybackPreferences.writeNoMediaPlaying();
IntentUtils.sendLocalBroadcast(context, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.cancel(R.id.notification_playing);
}
if (localDelete) {
// Do full update of this feed to get rid of the item
LocalFeedUpdater.updateFeed(media.getItem().getFeed(), context.getApplicationContext(), null);
} else {
// Gpodder: queue delete action for synchronization
FeedItem item = media.getItem();
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp()
.build();
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
}
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
return true;
}
@ -152,7 +173,7 @@ public class DBWriter {
* @param feedId ID of the Feed that should be deleted.
*/
public static Future<?> deleteFeed(final Context context, final long feedId) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final Feed feed = DBReader.getFeed(feedId);
if (feed == null) {
return;
@ -183,7 +204,7 @@ public class DBWriter {
*/
@NonNull
public static Future<?> deleteFeedItems(@NonNull Context context, @NonNull List<FeedItem> items) {
return dbExec.submit(() -> deleteFeedItemsSynchronous(context, items));
return runOnDbThread(() -> deleteFeedItemsSynchronous(context, items));
}
/**
@ -235,7 +256,7 @@ public class DBWriter {
* Deletes the entire playback history.
*/
public static Future<?> clearPlaybackHistory() {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.clearPlaybackHistory();
@ -248,7 +269,7 @@ public class DBWriter {
* Deletes the entire download log.
*/
public static Future<?> clearDownloadLog() {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.clearDownloadLog();
@ -281,7 +302,7 @@ public class DBWriter {
* @param date PlaybackCompletionDate for <code>media</code>
*/
public static Future<?> addItemToPlaybackHistory(final FeedMedia media, Date date) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
Log.d(TAG, "Adding item to playback history");
media.setPlaybackCompletionDate(date);
@ -300,7 +321,7 @@ public class DBWriter {
* @param status The DownloadStatus object.
*/
public static Future<?> addDownloadStatus(final DownloadResult status) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setDownloadStatus(status);
@ -322,7 +343,7 @@ public class DBWriter {
*/
public static Future<?> addQueueItemAt(final Context context, final long itemId,
final int index, final boolean performAutoDownload) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final List<FeedItem> queue = DBReader.getQueue(adapter);
@ -393,7 +414,7 @@ public class DBWriter {
*/
public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final boolean markAsUnplayed, final long... itemIds) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
if (itemIds.length < 1) {
return;
}
@ -476,7 +497,7 @@ public class DBWriter {
* Removes all FeedItem objects from the queue.
*/
public static Future<?> clearQueue() {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.clearQueue();
@ -495,12 +516,12 @@ public class DBWriter {
*/
public static Future<?> removeQueueItem(final Context context,
final boolean performAutoDownload, final FeedItem item) {
return dbExec.submit(() -> removeQueueItemSynchronous(context, performAutoDownload, item.getId()));
return runOnDbThread(() -> removeQueueItemSynchronous(context, performAutoDownload, item.getId()));
}
public static Future<?> removeQueueItem(final Context context, final boolean performAutoDownload,
final long... itemIds) {
return dbExec.submit(() -> removeQueueItemSynchronous(context, performAutoDownload, itemIds));
return runOnDbThread(() -> removeQueueItemSynchronous(context, performAutoDownload, itemIds));
}
private static void removeQueueItemSynchronous(final Context context,
@ -562,7 +583,7 @@ public class DBWriter {
}
public static Future<?> addFavoriteItem(final FeedItem item) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
adapter.addFavoriteItem(item);
adapter.close();
@ -573,7 +594,7 @@ public class DBWriter {
}
public static Future<?> removeFavoriteItem(final FeedItem item) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
adapter.removeFavoriteItem(item);
adapter.close();
@ -590,7 +611,7 @@ public class DBWriter {
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/
public static Future<?> moveQueueItemToTop(final long itemId, final boolean broadcastUpdate) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
LongList queueIdList = DBReader.getQueueIDList();
int index = queueIdList.indexOf(itemId);
if (index >= 0) {
@ -609,7 +630,7 @@ public class DBWriter {
*/
public static Future<?> moveQueueItemToBottom(final long itemId,
final boolean broadcastUpdate) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
LongList queueIdList = DBReader.getQueueIDList();
int index = queueIdList.indexOf(itemId);
if (index >= 0) {
@ -632,7 +653,7 @@ public class DBWriter {
*/
public static Future<?> moveQueueItem(final int from,
final int to, final boolean broadcastUpdate) {
return dbExec.submit(() -> moveQueueItemHelper(from, to, broadcastUpdate));
return runOnDbThread(() -> moveQueueItemHelper(from, to, broadcastUpdate));
}
/**
@ -669,7 +690,7 @@ public class DBWriter {
}
public static Future<?> resetPagedFeedPage(Feed feed) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.resetPagedFeedPage(feed);
@ -699,7 +720,7 @@ public class DBWriter {
*/
public static Future<?> markItemPlayed(final int played, final boolean broadcastUpdate,
final long... itemIds) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemRead(played, itemIds);
@ -729,7 +750,7 @@ public class DBWriter {
final int played,
final long mediaId,
final boolean resetMediaPosition) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemRead(played, itemId, mediaId,
@ -746,7 +767,7 @@ public class DBWriter {
* @param feedId ID of the Feed.
*/
public static Future<?> removeFeedNewFlag(final long feedId) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED, feedId);
@ -760,7 +781,7 @@ public class DBWriter {
* Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
*/
public static Future<?> removeAllNewFlags() {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED);
@ -771,7 +792,7 @@ public class DBWriter {
}
static Future<?> addNewFeed(final Context context, final Feed... feeds) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setCompleteFeed(feeds);
@ -789,7 +810,7 @@ public class DBWriter {
}
static Future<?> setCompleteFeed(final Feed... feeds) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setCompleteFeed(feeds);
@ -798,7 +819,7 @@ public class DBWriter {
}
public static Future<?> setItemList(final List<FeedItem> items) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.storeFeedItemlist(items);
@ -814,7 +835,7 @@ public class DBWriter {
* @param media The FeedMedia object.
*/
public static Future<?> setFeedMedia(final FeedMedia media) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setMedia(media);
@ -828,7 +849,7 @@ public class DBWriter {
* @param media The FeedMedia object.
*/
public static Future<?> setFeedMediaPlaybackInformation(final FeedMedia media) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedMediaPlaybackInformation(media);
@ -843,7 +864,7 @@ public class DBWriter {
* @param item The FeedItem object.
*/
public static Future<?> setFeedItem(final FeedItem item) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setSingleFeedItem(item);
@ -857,7 +878,7 @@ public class DBWriter {
*/
public static Future<?> updateFeedDownloadURL(final String original, final String updated) {
Log.d(TAG, "updateFeedDownloadURL(original: " + original + ", updated: " + updated + ")");
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedDownloadUrl(original, updated);
@ -871,7 +892,7 @@ public class DBWriter {
* @param preferences The FeedPreferences object.
*/
public static Future<?> setFeedPreferences(final FeedPreferences preferences) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedPreferences(preferences);
@ -901,7 +922,7 @@ public class DBWriter {
*/
public static Future<?> setFeedLastUpdateFailed(final long feedId,
final boolean lastUpdateFailed) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
@ -911,7 +932,7 @@ public class DBWriter {
}
public static Future<?> setFeedCustomTitle(Feed feed) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedCustomTitle(feed.getId(), feed.getCustomTitle());
@ -930,10 +951,10 @@ public class DBWriter {
public static Future<?> reorderQueue(@Nullable SortOrder sortOrder, final boolean broadcastUpdate) {
if (sortOrder == null) {
Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.");
return dbExec.submit(() -> { });
return runOnDbThread(() -> { });
}
final Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(sortOrder);
return dbExec.submit(() -> {
return runOnDbThread(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final List<FeedItem> queue = DBReader.getQueue(adapter);
@ -960,7 +981,7 @@ public class DBWriter {
public static Future<?> setFeedItemsFilter(final long feedId,
final Set<String> filterValues) {
Log.d(TAG, "setFeedItemsFilter() called with: " + "feedId = [" + feedId + "], filterValues = [" + filterValues + "]");
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemFilter(feedId, filterValues);
@ -974,7 +995,7 @@ public class DBWriter {
*
*/
public static Future<?> setFeedItemSortOrder(long feedId, @Nullable SortOrder sortOrder) {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemSortOrder(feedId, sortOrder);
@ -988,11 +1009,24 @@ public class DBWriter {
*/
@NonNull
public static Future<?> resetStatistics() {
return dbExec.submit(() -> {
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.resetAllMediaPlayedDuration();
adapter.close();
});
}
/**
* Submit to the DB thread only if caller is not already on the DB thread. Otherwise,
* just execute synchronously
*/
private static Future<?> runOnDbThread(Runnable runnable) {
if ("DatabaseExecutor".equals(Thread.currentThread().getName())) {
runnable.run();
return Futures.immediateFuture(null);
} else {
return dbExec.submit(runnable);
}
}
}

View File

@ -0,0 +1,13 @@
package de.danoeh.antennapod.core.util;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
public abstract class FeedUtil {
public static boolean shouldAutoDeleteItemsOnThatFeed(Feed feed) {
if (!UserPreferences.isAutoDelete()) {
return false;
}
return !feed.isLocalFeed() || UserPreferences.isAutoDeleteLocal();
}
}

View File

@ -77,6 +77,7 @@ public class UserPreferences {
public static final String PREF_SKIP_KEEPS_EPISODE = "prefSkipKeepsEpisode";
private static final String PREF_FAVORITE_KEEPS_EPISODE = "prefFavoriteKeepsEpisode";
private static final String PREF_AUTO_DELETE = "prefAutoDelete";
private static final String PREF_AUTO_DELETE_LOCAL = "prefAutoDeleteLocal";
public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
@ -366,6 +367,10 @@ public class UserPreferences {
return prefs.getBoolean(PREF_AUTO_DELETE, false);
}
public static boolean isAutoDeleteLocal() {
return prefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false);
}
public static int getSmartMarkAsPlayedSecs() {
return Integer.parseInt(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
}

View File

@ -216,6 +216,7 @@
<string name="stream_label">Stream</string>
<string name="delete_label">Delete</string>
<string name="delete_failed">Unable to delete file. Rebooting the device could help.</string>
<string name="delete_local_failed">Unable to delete file. Try re-connecting the local folder from the podcast info screen.</string>
<string name="delete_episode_label">Delete episode</string>
<plurals name="deleted_multi_episode_batch_label">
<item quantity="one">1 downloaded episode deleted.</item>
@ -259,6 +260,7 @@
<string name="skip_episode_label">Skip episode</string>
<string name="reset_position">Reset playback position</string>
<string name="no_items_selected">No items selected</string>
<string name="delete_local_feed_warning_body">Deleting removes the episode from AntennaPod and deletes the media file from your device storage. It cannot be downloaded again through AntennaPod.</string>
<!-- Download messages and labels -->
<string name="download_successful">successful</string>
@ -401,6 +403,9 @@
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>
<string name="pref_auto_delete_sum">Delete episode when playback completes</string>
<string name="pref_auto_delete_title">Auto delete</string>
<string name="pref_auto_local_delete_title">Auto delete from local folders</string>
<string name="pref_auto_local_delete_sum">Include local folders in Auto delete functionality</string>
<string name="pref_auto_local_delete_dialog_body">Note that for local folders this will remove episodes from AntennaPod and delete their media files from your device storage. They cannot be downloaded again through AntennaPod. Enable auto delete?</string>
<string name="pref_smart_mark_as_played_sum">Mark episodes as played even if less than a certain amount of seconds of playing time is still left</string>
<string name="pref_smart_mark_as_played_title">Smart mark as played</string>
<string name="pref_skip_keeps_episodes_sum">Keep episodes when they are skipped</string>