diff options
author | Matej Drobnič <matej@matejdro.com> | 2023-10-22 16:53:41 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-22 16:53:41 +0200 |
commit | 346365b8d0aef14e79da921056d9438fe9d2663e (patch) | |
tree | 5fa0082c45493ffa16ffd0a63a3db722fd3206a1 | |
parent | fa75317bce95bdae152fa1171894d4c025e3921c (diff) | |
download | AntennaPod-346365b8d0aef14e79da921056d9438fe9d2663e.zip |
Delete local feed episodes (#6400)
17 files changed, 224 insertions, 67 deletions
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 @@ -200,6 +201,22 @@ public class PreferencesTest { } @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); clickPreference(R.string.playback_speed); 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<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(); + } +} 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 @@ -34,6 +34,12 @@ 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" android:key="prefFavoriteKeepsEpisode" diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java index 03881ee4f..52b2d61dc 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java @@ -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); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index b98dbfad7..c82d6f975 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -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()); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index 11e82af27..f7e8592e1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -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(); + } 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<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); + } + } } 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 @@ <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> |