From 346365b8d0aef14e79da921056d9438fe9d2663e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Drobni=C4=8D?= Date: Sun, 22 Oct 2023 16:53:41 +0200 Subject: Delete local feed episodes (#6400) --- .../de/test/antennapod/ui/PreferencesTest.java | 17 +++ .../adapter/actionbutton/DeleteActionButton.java | 14 ++- .../antennapod/fragment/AddFeedFragment.java | 4 +- .../antennapod/fragment/FeedItemlistFragment.java | 1 - .../antennapod/fragment/FeedSettingsFragment.java | 1 - .../actions/EpisodeMultiSelectActionHandler.java | 3 +- .../preferences/DownloadsPreferencesFragment.java | 27 +++++ .../fragment/swipeactions/DeleteSwipeAction.java | 12 +- .../menuhandler/FeedItemMenuHandler.java | 14 ++- .../danoeh/antennapod/view/LocalDeleteModal.java | 32 ++++++ app/src/main/res/xml/preferences_downloads.xml | 6 + .../antennapod/core/feed/LocalFeedUpdater.java | 5 - .../core/service/playback/PlaybackService.java | 4 +- .../danoeh/antennapod/core/storage/DBWriter.java | 128 +++++++++++++-------- .../de/danoeh/antennapod/core/util/FeedUtil.java | 13 +++ .../storage/preferences/UserPreferences.java | 5 + ui/i18n/src/main/res/values/strings.xml | 5 + 17 files changed, 224 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/de/danoeh/antennapod/view/LocalDeleteModal.java create mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/FeedUtil.java diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 1f387b24b..24c20242a 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -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); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DeleteActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DeleteActionButton.java index 096d060c1..16a5a161c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DeleteActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DeleteActionButton.java @@ -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; } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 6a72348bc..29be41727 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -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"); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index 6b6fedd1f..1c949218a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -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); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index 0e4c883cf..1df1e02a2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -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); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java index a14bfcd16..618c411ea 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java @@ -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); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadsPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadsPreferencesFragment.java index 7b0c3efdf..c17066fef 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadsPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadsPreferencesFragment.java @@ -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(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/DeleteSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/DeleteSwipeAction.java index a4c69eb2b..e196b96b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/DeleteSwipeAction.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/DeleteSwipeAction.java @@ -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()); } } diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index de4cec8fe..a72b32497 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -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()); } }; diff --git a/app/src/main/java/de/danoeh/antennapod/view/LocalDeleteModal.java b/app/src/main/java/de/danoeh/antennapod/view/LocalDeleteModal.java new file mode 100644 index 000000000..4241cadca --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/LocalDeleteModal.java @@ -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 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(); + } +} diff --git a/app/src/main/res/xml/preferences_downloads.xml b/app/src/main/res/xml/preferences_downloads.xml index e1a1b9b00..b0a41dfb5 100644 --- a/app/src/main/res/xml/preferences_downloads.xml +++ b/app/src/main/res/xml/preferences_downloads.xml @@ -33,6 +33,12 @@ android:key="prefAutoDelete" android:summary="@string/pref_auto_delete_sum" android:title="@string/pref_auto_delete_title"/> + 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(); + } 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); + 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); - } + 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 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 media */ 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 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 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 permutor = FeedItemPermutors.getPermutor(sortOrder); - return dbExec.submit(() -> { + return runOnDbThread(() -> { final PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); final List queue = DBReader.getQueue(adapter); @@ -960,7 +981,7 @@ public class DBWriter { public static Future setFeedItemsFilter(final long feedId, final Set 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); + } + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUtil.java new file mode 100644 index 000000000..201207816 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUtil.java @@ -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(); + } +} diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java index c8ad3eec5..80a58525d 100644 --- a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java @@ -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")); } diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index 585701a9f..c585a6616 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -216,6 +216,7 @@ Stream Delete Unable to delete file. Rebooting the device could help. + Unable to delete file. Try re-connecting the local folder from the podcast info screen. Delete episode 1 downloaded episode deleted. @@ -259,6 +260,7 @@ Skip episode Reset playback position No items selected + Deleting removes the episode from AntennaPod and deletes the media file from your device storage. It cannot be downloaded again through AntennaPod. successful @@ -401,6 +403,9 @@ Jump to next queue item when playback completes Delete episode when playback completes Auto delete + Auto delete from local folders + Include local folders in Auto delete functionality + 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? Mark episodes as played even if less than a certain amount of seconds of playing time is still left Smart mark as played Keep episodes when they are skipped -- cgit v1.2.3