diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 53faf8eb0..de76c4066 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -33,7 +33,7 @@ import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.ui.common.ThemeSwitcher; import de.danoeh.antennapod.core.service.download.DownloadRequestCreator; import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface; import de.danoeh.antennapod.core.util.DownloadErrorLabel; import de.danoeh.antennapod.databinding.EditTextDialogBinding; @@ -452,7 +452,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedInFeedlist()) { openFeed(); } else { - DBTasks.updateFeed(this, feed, false); + FeedDatabaseWriter.updateFeed(this, feed, false); didPressSubscribe = true; handleUpdatedFeedStatus(); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index 7bd057988..ed11e6910 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -28,7 +28,7 @@ import androidx.core.app.ActivityCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.ui.common.ThemeSwitcher; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.databinding.OpmlSelectionBinding; import de.danoeh.antennapod.model.feed.Feed; @@ -102,7 +102,7 @@ public class OpmlImportActivity extends AppCompatActivity { Feed feed = new Feed(element.getXmlUrl(), null, element.getText() != null ? element.getText() : "Unknown podcast"); feed.setItems(Collections.emptyList()); - DBTasks.updateFeed(this, feed, false); + FeedDatabaseWriter.updateFeed(this, feed, false); } FeedUpdateManager.runOnce(this); }) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java index 983ae3df9..e0a15191e 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java @@ -1,17 +1,22 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; +import android.util.Log; import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; +import org.greenrobot.eventbus.EventBus; public class PlayActionButton extends ItemActionButton { + private static final String TAG = "PlayActionButton"; public PlayActionButton(FeedItem item) { super(item); @@ -36,7 +41,12 @@ public class PlayActionButton extends ItemActionButton { return; } if (!media.fileExists()) { - DBTasks.notifyMissingFeedMediaFile(context, media); + Log.i(TAG, "Missing episode. Will update the database now."); + media.setDownloaded(false); + media.setLocalFileUrl(null); + DBWriter.setFeedMedia(media); + EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); + EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found))); return; } new PlaybackServiceStarter(context, media) 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 16bfaa00a..5074c5b64 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -29,7 +29,7 @@ import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.databinding.AddfeedBinding; import de.danoeh.antennapod.databinding.EditTextDialogBinding; @@ -205,7 +205,7 @@ public class AddFeedFragment extends Fragment { Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, title); dirFeed.setItems(Collections.emptyList()); dirFeed.setSortOrder(SortOrder.EPISODE_TITLE_A_Z); - Feed fromDatabase = DBTasks.updateFeed(getContext(), dirFeed, false); + Feed fromDatabase = FeedDatabaseWriter.updateFeed(getContext(), dirFeed, false); FeedUpdateManager.runOnce(requireContext(), fromDatabase); return fromDatabase; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java index a8d6053fe..49ea28765 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -35,7 +35,7 @@ import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; @@ -340,7 +340,7 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu throw new IllegalArgumentException("Unable to retrieve document tree"); } feed.setDownloadUrl(Feed.PREFIX_LOCAL_FOLDER + uri.toString()); - DBTasks.updateFeed(getContext(), feed, true); + FeedDatabaseWriter.updateFeed(getContext(), feed, true); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 8c976ae7c..76b53a264 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -42,10 +42,10 @@ import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; -import de.danoeh.antennapod.core.storage.FeedSearcher; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.net.discovery.CombinedSearcher; +import de.danoeh.antennapod.storage.database.DBReader; import de.danoeh.antennapod.view.EmptyViewHandler; import de.danoeh.antennapod.view.EpisodeItemListRecyclerView; import de.danoeh.antennapod.view.LiftOnScrollListener; @@ -407,8 +407,8 @@ public class SearchFragment extends Fragment implements EpisodeItemListAdapter.O return new Pair<>(Collections.emptyList(), Collections.emptyList()); } long feed = getArguments().getLong(ARG_FEED); - List items = FeedSearcher.searchFeedItems(query, feed); - List feeds = FeedSearcher.searchFeeds(query); + List items = DBReader.searchFeedItems(feed, query); + List feeds = DBReader.searchFeeds(query); return new Pair<>(items, feeds); } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java index a90ad157e..9b3b40b45 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java @@ -6,9 +6,9 @@ import android.content.Intent; import android.util.Log; import de.danoeh.antennapod.core.ClientConfigurator; +import de.danoeh.antennapod.core.storage.AutoDownloadManager; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; -import de.danoeh.antennapod.core.storage.DBTasks; // modified from http://developer.android.com/training/monitoring-device-state/battery-monitoring.html // and ConnectivityActionReceiver.java @@ -32,7 +32,7 @@ public class PowerConnectionReceiver extends BroadcastReceiver { // downloading now. They shouldn't mind. // autodownloadUndownloadedItems will make sure we're on the right wifi networks, // etc... so we don't have to worry about it. - DBTasks.autodownloadUndownloadedItems(context); + AutoDownloadManager.autodownloadUndownloadedItems(context); } else { // if we're not supposed to be auto-downloading when we're not charging, stop it if (!UserPreferences.isEnableAutodownloadOnBattery()) { diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java index 788359a4e..d6641a1e1 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java @@ -12,7 +12,7 @@ import java.util.Collections; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.ClientConfigurator; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.model.feed.Feed; @@ -46,7 +46,7 @@ public class SPAReceiver extends BroadcastReceiver{ for (String url : feedUrls) { Feed feed = new Feed(url, null, "Unknown podcast"); feed.setItems(Collections.emptyList()); - DBTasks.updateFeed(context, feed, false); + FeedDatabaseWriter.updateFeed(context, feed, false); } Toast.makeText(context, R.string.sp_apps_importing_feeds_msg, Toast.LENGTH_LONG).show(); FeedUpdateManager.runOnce(context); diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java index a4b0436bd..358b75e9c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java @@ -8,7 +8,7 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.util.Log; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.storage.importexport.OpmlElement; import de.danoeh.antennapod.storage.importexport.OpmlReader; @@ -146,7 +146,7 @@ public class OpmlBackupAgent extends BackupAgentHelper { for (OpmlElement opmlElem : opmlElements) { Feed feed = new Feed(opmlElem.getXmlUrl(), null, opmlElem.getText()); feed.setItems(Collections.emptyList()); - DBTasks.updateFeed(mContext, feed, false); + FeedDatabaseWriter.updateFeed(mContext, feed, false); } FeedUpdateManager.runOnce(mContext); } catch (XmlPullParserException e) { 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 096a3757e..a4dab1996 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 @@ -31,7 +31,7 @@ import de.danoeh.antennapod.core.util.FastDocumentFile; import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat; import de.danoeh.antennapod.model.download.DownloadResult; import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.parser.feed.util.DateUtils; import de.danoeh.antennapod.model.download.DownloadError; @@ -82,7 +82,7 @@ public class LocalFeedUpdater { feed.setItems(new ArrayList<>()); } //make sure it is the latest 'version' of this feed from the db (all items etc) - feed = DBTasks.updateFeed(context, feed, false); + feed = FeedDatabaseWriter.updateFeed(context, feed, false); // list files in feed folder List allFiles = FastDocumentFile.list(context, folderUri); @@ -127,7 +127,7 @@ public class LocalFeedUpdater { feed.setDescription(context.getString(R.string.local_feed_description)); feed.setAuthor(context.getString(R.string.local_folder)); - DBTasks.updateFeed(context, feed, true); + FeedDatabaseWriter.updateFeed(context, feed, true); } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java index ac1f739af..45b6aee04 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java @@ -24,8 +24,9 @@ import de.danoeh.antennapod.core.service.download.DownloadRequestCreator; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.service.download.NewEpisodesNotification; import de.danoeh.antennapod.core.service.download.handler.FeedParserTask; +import de.danoeh.antennapod.core.storage.AutoDownloadManager; import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.net.common.NetworkUtils; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; @@ -99,7 +100,7 @@ public class FeedUpdateWorker extends Worker { refreshFeeds(toUpdate, force); notificationManager.cancel(R.id.notification_updating_feeds); - DBTasks.autodownloadUndownloadedItems(getApplicationContext()); + AutoDownloadManager.autodownloadUndownloadedItems(getApplicationContext()); return Result.success(); } @@ -199,7 +200,7 @@ public class FeedUpdateWorker extends Worker { return; } feedHandlerResult.feed.setLastRefreshAttempt(System.currentTimeMillis()); - Feed savedFeed = DBTasks.updateFeed(getApplicationContext(), feedHandlerResult.feed, false); + Feed savedFeed = FeedDatabaseWriter.updateFeed(getApplicationContext(), feedHandlerResult.feed, false); if (request.getFeedfileId() == 0) { return; // No download logs for new subscriptions 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 f40d51933..6f7068d24 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 @@ -72,7 +72,6 @@ import de.danoeh.antennapod.core.service.QuickSettingsTileService; import de.danoeh.antennapod.core.service.playback.PlaybackServiceTaskManager.SleepTimer; import de.danoeh.antennapod.storage.database.DBReader; 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.ChapterUtils; import de.danoeh.antennapod.core.util.FeedItemUtil; @@ -1842,7 +1841,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { return; } - List results = FeedSearcher.searchFeedItems(query, 0); + List results = DBReader.searchFeedItems(0, query); if (results.size() > 0 && results.get(0).getMedia() != null) { FeedMedia media = results.get(0).getMedia(); startPlaying(media, false); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java new file mode 100644 index 000000000..bc5244381 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java @@ -0,0 +1,54 @@ +package de.danoeh.antennapod.core.storage; + +import android.content.Context; +import android.util.Log; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public abstract class AutoDownloadManager { + private static final String TAG = "AutoDownloadManager"; + + /** + * Executor service used by the autodownloadUndownloadedEpisodes method. + */ + private static final ExecutorService autodownloadExec; + + private static AutomaticDownloadAlgorithm downloadAlgorithm = new AutomaticDownloadAlgorithm(); + + static { + autodownloadExec = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setPriority(Thread.MIN_PRIORITY); + return t; + }); + } + + /** + * Looks for non-downloaded episodes in the queue or list of unread items and request a download if + * 1. Network is available + * 2. The device is charging or the user allows auto download on battery + * 3. There is free space in the episode cache + * This method is executed on an internal single thread executor. + * + * @param context Used for accessing the DB. + * @return A Future that can be used for waiting for the methods completion. + */ + public static Future autodownloadUndownloadedItems(final Context context) { + Log.d(TAG, "autodownloadUndownloadedItems"); + return autodownloadExec.submit(downloadAlgorithm.autoDownloadUndownloadedItems(context)); + } + + /** + * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller + * 'playbackCompletionDate'-value will be deleted first. + *

+ * This method should NOT be executed on the GUI thread. + * + * @param context Used for accessing the DB. + */ + public static void performAutoCleanup(final Context context) { + EpisodeCleanupAlgorithmFactory.build().performCleanup(context); + } +} 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 496eaf374..1e5b416e2 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,6 +2,7 @@ package de.danoeh.antennapod.core.storage; import android.app.backup.BackupManager; import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.util.Log; @@ -27,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -366,7 +368,7 @@ public class DBWriter { adapter.close(); if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); + AutoDownloadManager.autodownloadUndownloadedItems(context); } }); @@ -460,7 +462,7 @@ public class DBWriter { } adapter.close(); if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); + AutoDownloadManager.autodownloadUndownloadedItems(context); } }); } @@ -569,7 +571,7 @@ public class DBWriter { } adapter.close(); if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); + AutoDownloadManager.autodownloadUndownloadedItems(context); } } @@ -1016,6 +1018,38 @@ public class DBWriter { }); } + /** + * Removes the feed with the given download url. This method should NOT be executed on the GUI thread. + * + * @param context Used for accessing the db + * @param downloadUrl URL of the feed. + */ + public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) { + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + Cursor cursor = adapter.getFeedCursorDownloadUrls(); + long feedId = 0; + if (cursor.moveToFirst()) { + do { + if (cursor.getString(1).equals(downloadUrl)) { + feedId = cursor.getLong(0); + } + } while (cursor.moveToNext()); + } + cursor.close(); + adapter.close(); + + if (feedId != 0) { + try { + deleteFeed(context, feedId).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } else { + Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl); + } + } + /** * Submit to the DB thread only if caller is not already on the DB thread. Otherwise, * just execute synchronously diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java similarity index 63% rename from core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java rename to core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java index b7112e6fd..7c21bd7c9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java @@ -1,26 +1,19 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; -import android.database.Cursor; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.VisibleForTesting; -import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink; import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.FeedListUpdateEvent; -import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.model.download.DownloadError; import de.danoeh.antennapod.model.download.DownloadResult; 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.net.sync.model.EpisodeAction; import de.danoeh.antennapod.storage.database.DBReader; import de.danoeh.antennapod.storage.database.PodDBAdapter; -import de.danoeh.antennapod.storage.database.mapper.FeedCursorMapper; import de.danoeh.antennapod.storage.preferences.UserPreferences; import org.greenrobot.eventbus.EventBus; @@ -29,116 +22,13 @@ import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; /** - * Provides methods for doing common tasks that use DBReader and DBWriter. + * Creates and updates feeds in the database. */ -public final class DBTasks { - private static final String TAG = "DBTasks"; - - /** - * Executor service used by the autodownloadUndownloadedEpisodes method. - */ - private static final ExecutorService autodownloadExec; - - private static AutomaticDownloadAlgorithm downloadAlgorithm = new AutomaticDownloadAlgorithm(); - - static { - autodownloadExec = Executors.newSingleThreadExecutor(r -> { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - }); - } - - private DBTasks() { - } - - /** - * Removes the feed with the given download url. This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the db - * @param downloadUrl URL of the feed. - */ - public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) { - PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - Cursor cursor = adapter.getFeedCursorDownloadUrls(); - long feedID = 0; - if (cursor.moveToFirst()) { - do { - if (cursor.getString(1).equals(downloadUrl)) { - feedID = cursor.getLong(0); - } - } while (cursor.moveToNext()); - } - cursor.close(); - adapter.close(); - - if (feedID != 0) { - try { - DBWriter.deleteFeed(context, feedID).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } else { - Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl); - } - } - - /** - * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's - * values in the DB and send a FeedItemEvent. - */ - public static void notifyMissingFeedMediaFile(final Context context, final FeedMedia media) { - Log.i(TAG, "The feedmanager was notified about a missing episode. It will update its database now."); - media.setDownloaded(false); - media.setLocalFileUrl(null); - DBWriter.setFeedMedia(media); - EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); - EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found))); - } - - /** - * Looks for non-downloaded episodes in the queue or list of unread items and request a download if - * 1. Network is available - * 2. The device is charging or the user allows auto download on battery - * 3. There is free space in the episode cache - * This method is executed on an internal single thread executor. - * - * @param context Used for accessing the DB. - * @return A Future that can be used for waiting for the methods completion. - */ - public static Future autodownloadUndownloadedItems(final Context context) { - Log.d(TAG, "autodownloadUndownloadedItems"); - return autodownloadExec.submit(downloadAlgorithm.autoDownloadUndownloadedItems(context)); - } - - /** - * For testing purpose only. - */ - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - public static void setDownloadAlgorithm(AutomaticDownloadAlgorithm newDownloadAlgorithm) { - downloadAlgorithm = newDownloadAlgorithm; - } - - /** - * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller - * 'playbackCompletionDate'-value will be deleted first. - *

- * This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the DB. - */ - public static void performAutoCleanup(final Context context) { - EpisodeCleanupAlgorithmFactory.build().performCleanup(context); - } +public abstract class FeedDatabaseWriter { + private static final String TAG = "FeedDbWriter"; private static Feed searchFeedByIdentifyingValueOrID(Feed feed) { if (feed.getId() != 0) { @@ -191,10 +81,6 @@ public final class DBTasks { * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed. * These FeedItems will be marked as unread with the exception of the most recent FeedItem. - *

- * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior. - *

- * This method should NOT be executed on the GUI thread. * * @param context Used for accessing the DB. * @param newFeed The new Feed object. @@ -373,70 +259,4 @@ public final class DBTasks { + "\nID: " + item.getItemIdentifier() + ((item.getMedia() == null) ? "" : "\nURL: " + item.getMedia().getDownloadUrl()); } - - /** - * Searches the FeedItems of a specific Feed for a given string. - * - * @param feedID The id of the feed whose items should be searched. - * @param query The search string. - * @return A FutureTask object that executes the search request - * and returns the search result as a List of FeedItems. - */ - public static FutureTask> searchFeedItems(final long feedID, final String query) { - return new FutureTask<>(new QueryTask>() { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItems(feedID, query); - List items = DBReader.extractItemlistFromCursor(searchResult); - DBReader.loadAdditionalFeedItemListData(items); - setResult(items); - searchResult.close(); - } - }); - } - - public static FutureTask> searchFeeds(final String query) { - return new FutureTask<>(new QueryTask>() { - @Override - public void execute(PodDBAdapter adapter) { - Cursor cursor = adapter.searchFeeds(query); - List items = new ArrayList<>(); - if (cursor.moveToFirst()) { - do { - items.add(FeedCursorMapper.convert(cursor)); - } while (cursor.moveToNext()); - } - setResult(items); - cursor.close(); - } - }); - } - - /** - * A runnable which should be used for database queries. The onCompletion - * method is executed on the database executor to handle Cursors correctly. - * This class automatically creates a PodDBAdapter object and closes it when - * it is no longer in use. - */ - abstract static class QueryTask implements Callable { - private T result; - - public QueryTask() { - } - - @Override - public T call() throws Exception { - PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - execute(adapter); - adapter.close(); - return result; - } - - public abstract void execute(PodDBAdapter adapter); - - void setResult(T result) { - this.result = result; - } - } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java deleted file mode 100644 index 68ce7b7ef..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import androidx.annotation.NonNull; -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.model.feed.FeedItem; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -/** - * Performs search on Feeds and FeedItems. - */ -public class FeedSearcher { - private FeedSearcher() { - - } - - @NonNull - public static List searchFeedItems(final String query, final long selectedFeed) { - try { - FutureTask> itemSearchTask = DBTasks.searchFeedItems(selectedFeed, query); - itemSearchTask.run(); - return itemSearchTask.get(); - } catch (ExecutionException | InterruptedException e) { - e.printStackTrace(); - return Collections.emptyList(); - } - } - - @NonNull - public static List searchFeeds(final String query) { - try { - FutureTask> feedSearchTask = DBTasks.searchFeeds(query); - feedSearchTask.run(); - return feedSearchTask.get(); - } catch (ExecutionException | InterruptedException e) { - e.printStackTrace(); - return Collections.emptyList(); - } - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java index 2dca8a9e7..28ada01f8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java @@ -43,7 +43,7 @@ import de.danoeh.antennapod.event.SyncServiceEvent; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.net.common.AntennapodHttpClient; import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.FeedDatabaseWriter; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueStorage; import de.danoeh.antennapod.core.util.FeedItemUtil; @@ -158,7 +158,7 @@ public class SyncService extends Worker { if (!UrlChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) { Feed feed = new Feed(downloadUrl, null, "Unknown podcast"); feed.setItems(Collections.emptyList()); - Feed newFeed = DBTasks.updateFeed(getApplicationContext(), feed, false); + Feed newFeed = FeedDatabaseWriter.updateFeed(getApplicationContext(), feed, false); FeedUpdateManager.runOnce(getApplicationContext(), newFeed); } } @@ -166,7 +166,7 @@ public class SyncService extends Worker { // remove subscription if not just subscribed (again) for (String downloadUrl : subscriptionChanges.getRemoved()) { if (!queuedAddedFeeds.contains(downloadUrl)) { - DBTasks.removeFeedWithDownloadUrl(getApplicationContext(), downloadUrl); + DBWriter.removeFeedWithDownloadUrl(getApplicationContext(), downloadUrl); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java index 8097a4cb1..cafbde368 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java @@ -2,8 +2,8 @@ package de.danoeh.antennapod.core.util.download; import android.content.Context; import android.util.Log; +import de.danoeh.antennapod.core.storage.AutoDownloadManager; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.net.common.NetworkUtils; public abstract class NetworkConnectionChangeHandler { @@ -17,7 +17,7 @@ public abstract class NetworkConnectionChangeHandler { public static void networkChangedDetected() { if (NetworkUtils.isAutoDownloadAllowed()) { Log.d(TAG, "auto-dl network available, starting auto-download"); - DBTasks.autodownloadUndownloadedItems(context); + AutoDownloadManager.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, // otherwise cancel all downloads if (NetworkUtils.isNetworkRestricted()) { diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java index 5ff09287d..a0632b345 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java @@ -115,7 +115,7 @@ public class DbCleanupTests { List files = new ArrayList<>(); populateItems(numItems, feed, items, files, FeedItem.PLAYED, false, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { if (i < EPISODE_CACHE_SIZE) { assertTrue(files.get(i).exists()); @@ -174,7 +174,7 @@ public class DbCleanupTests { List files = new ArrayList<>(); populateItems(numItems, feed, items, files, FeedItem.UNPLAYED, false, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (File file : files) { assertTrue(file.exists()); } @@ -190,7 +190,7 @@ public class DbCleanupTests { List files = new ArrayList<>(); populateItems(numItems, feed, items, files, FeedItem.PLAYED, true, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (File file : files) { assertTrue(file.exists()); } @@ -230,7 +230,7 @@ public class DbCleanupTests { List files = new ArrayList<>(); populateItems(numItems, feed, items, files, FeedItem.PLAYED, false, true); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (File file : files) { assertTrue(file.exists()); } diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java index 9f9902ad0..243bef941 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java @@ -114,7 +114,7 @@ public class DbNullCleanupAlgorithmTest { //noinspection ConstantConditions assertTrue(item.getMedia().getId() != 0); } - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { assertTrue(files.get(i).exists()); } diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java index dc8f4f45b..c14c3c2aa 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java @@ -40,7 +40,7 @@ public class DbQueueCleanupAlgorithmTest extends DbCleanupTests { List files = new ArrayList<>(); populateItems(numItems, feed, items, files, FeedItem.UNPLAYED, false, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { if (i < EPISODE_CACHE_SIZE) { assertTrue(files.get(i).exists()); diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java index 9f7ba095b..1357f4d4e 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** - * Test class for {@link DBTasks}. + * Test class for {@link FeedDatabaseWriter}. */ @RunWith(RobolectricTestRunner.class) public class DbTasksTest { @@ -77,7 +77,7 @@ public class DbTasksTest { feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(), FeedItem.UNPLAYED, feed)); } - Feed newFeed = DBTasks.updateFeed(context, feed, false); + Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false); assertEquals(feed.getId(), newFeed.getId()); assertTrue(feed.getId() != 0); @@ -97,8 +97,8 @@ public class DbTasksTest { feed1.setItems(new ArrayList<>()); feed2.setItems(new ArrayList<>()); - Feed savedFeed1 = DBTasks.updateFeed(context, feed1, false); - Feed savedFeed2 = DBTasks.updateFeed(context, feed2, false); + Feed savedFeed1 = FeedDatabaseWriter.updateFeed(context, feed1, false); + Feed savedFeed2 = FeedDatabaseWriter.updateFeed(context, feed2, false); assertTrue(savedFeed1.getId() != savedFeed2.getId()); } @@ -135,7 +135,7 @@ public class DbTasksTest { new Date(i), FeedItem.UNPLAYED, feed)); } - final Feed newFeed = DBTasks.updateFeed(context, feed, false); + final Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false); assertNotSame(newFeed, feed); updatedFeedTest(newFeed, feedID, itemIDs, numItemsOld, numItemsNew); @@ -167,7 +167,7 @@ public class DbTasksTest { list.add(item); feed.setItems(list); - final Feed newFeed = DBTasks.updateFeed(context, feed, false); + final Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false); assertNotSame(newFeed, feed); final Feed feedFromDB = DBReader.getFeed(newFeed.getId()); @@ -190,7 +190,7 @@ public class DbTasksTest { // delete some items feed.getItems().subList(0, 2).clear(); - Feed newFeed = DBTasks.updateFeed(context, feed, true); + Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, true); assertEquals(8, newFeed.getItems().size()); // 10 - 2 = 8 items Feed feedFromDB = DBReader.getFeed(newFeed.getId()); @@ -217,7 +217,7 @@ public class DbTasksTest { FeedItem item = feed.getItemAtIndex(0); item.setItemIdentifier("id 0-duplicate"); item.setTitle("item 0 duplicate"); - Feed newFeed = DBTasks.updateFeed(context, feed, false); + Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false); assertEquals(10, newFeed.getItems().size()); // id 1-duplicate replaces because the stream url is the same Feed feedFromDB = DBReader.getFeed(newFeed.getId()); diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java index 150ab04ba..b464a2508 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java @@ -35,7 +35,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests { List files = new ArrayList<>(); populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { if (i < EPISODE_CACHE_SIZE) { assertTrue("Only enough items should be deleted", files.get(i).exists()); @@ -53,7 +53,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests { List files = new ArrayList<>(); populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, true, false); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { if (i < EPISODE_CACHE_SIZE) { assertTrue("Only enough items should be deleted", files.get(i).exists()); @@ -71,7 +71,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests { List files = new ArrayList<>(); populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, true); - DBTasks.performAutoCleanup(context); + AutoDownloadManager.performAutoCleanup(context); for (int i = 0; i < files.size(); i++) { assertTrue("Favorite episodes should should not be deleted", files.get(i).exists()); } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java index 89967c24b..f0239d8f5 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java @@ -857,4 +857,28 @@ public final class DBReader { adapter.close(); return result; } + + public static List searchFeedItems(final long feedId, final String query) { + PodDBAdapter adapter = PodDBAdapter.getInstance().open(); + Cursor searchResult = adapter.searchItems(feedId, query); + List items = extractItemlistFromCursor(searchResult); + loadAdditionalFeedItemListData(items); + searchResult.close(); + adapter.close(); + return items; + } + + public static List searchFeeds(final String query) { + PodDBAdapter adapter = PodDBAdapter.getInstance(); + Cursor cursor = adapter.searchFeeds(query); + List items = new ArrayList<>(); + if (cursor.moveToFirst()) { + do { + items.add(FeedCursorMapper.convert(cursor)); + } while (cursor.moveToNext()); + } + cursor.close(); + adapter.close(); + return items; + } }