summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorByteHamster <ByteHamster@users.noreply.github.com>2023-03-14 21:03:45 +0100
committerGitHub <noreply@github.com>2023-03-14 21:03:45 +0100
commit4f7f49e1e714ce41320fff569272a1423198b2f3 (patch)
tree0adeefb8e26863cbccc204f11437684960114d86 /core
parent2c0b970044b3180786e165588d5d94ada0c90797 (diff)
downloadAntennaPod-4f7f49e1e714ce41320fff569272a1423198b2f3.zip
Move feed download to worker (#6375)
Feed downloads are now independent from episode downloads. This makes it easier to use WorkManager for refreshing. Also, it will make it easier to add different refresh intervals in the future.
Diffstat (limited to 'core')
-rw-r--r--core/build.gradle1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java166
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java139
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java101
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java167
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java112
-rw-r--r--core/src/main/res/values/arrays.xml22
-rw-r--r--core/src/main/res/values/ids.xml1
14 files changed, 352 insertions, 422 deletions
diff --git a/core/build.gradle b/core/build.gradle
index a70a1e1c0..02c273db1 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -62,6 +62,7 @@ dependencies {
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbusVersion"
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
+ implementation 'com.annimon:stream:1.2.2'
implementation "com.google.android.exoplayer:exoplayer-core:$exoPlayerVersion"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion"
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 9046b7165..79c6dd6bc 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,9 +8,8 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -30,6 +29,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import de.danoeh.antennapod.core.export.opml.OpmlElement;
import de.danoeh.antennapod.core.export.opml.OpmlReader;
@@ -144,9 +144,10 @@ public class OpmlBackupAgent extends BackupAgentHelper {
mChecksum = digester == null ? null : digester.digest();
for (OpmlElement opmlElem : opmlElements) {
Feed feed = new Feed(opmlElem.getXmlUrl(), null, opmlElem.getText());
- DownloadRequest request = DownloadRequestCreator.create(feed).build();
- DownloadServiceInterface.get().download(mContext, false, request);
+ feed.setItems(Collections.emptyList());
+ DBTasks.updateFeed(mContext, feed, false);
}
+ FeedUpdateManager.runOnce(mContext);
} catch (XmlPullParserException e) {
Log.e(TAG, "Error while parsing the OPML file", e);
} catch (IOException e) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index 9ce89ebe2..e30b49280 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -6,7 +6,7 @@ import android.content.Intent;
import android.util.Log;
import de.danoeh.antennapod.core.ClientConfigurator;
-import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
/**
* Refreshes all feeds when it receives an intent
@@ -20,7 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
ClientConfigurator.initialize(context);
- AutoUpdateManager.runOnce(context);
+ FeedUpdateManager.runOnce(context);
}
}
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 49c5211b0..bbf0dc357 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
@@ -1,47 +1,177 @@
package de.danoeh.antennapod.core.service;
+import android.app.Notification;
import android.content.Context;
-import androidx.annotation.NonNull;
import android.util.Log;
-
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.work.ForegroundInfo;
+import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
-
+import com.annimon.stream.Collectors;
+import com.annimon.stream.Stream;
import de.danoeh.antennapod.core.ClientConfigurator;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
+import de.danoeh.antennapod.core.service.download.DefaultDownloaderFactory;
+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.FeedSyncTask;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
+import de.danoeh.antennapod.model.download.DownloadError;
+import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-public class FeedUpdateWorker extends Worker {
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+public class FeedUpdateWorker extends Worker {
private static final String TAG = "FeedUpdateWorker";
- public static final String PARAM_RUN_ONCE = "runOnce";
+ private final NewEpisodesNotification newEpisodesNotification;
public FeedUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
+ newEpisodesNotification = new NewEpisodesNotification();
}
@Override
@NonNull
public Result doWork() {
- final boolean isRunOnce = getInputData().getBoolean(PARAM_RUN_ONCE, false);
- Log.d(TAG, "doWork() : isRunOnce = " + isRunOnce);
ClientConfigurator.initialize(getApplicationContext());
+ newEpisodesNotification.loadCountersBeforeRefresh();
- if (NetworkUtils.networkAvailable() && NetworkUtils.isFeedRefreshAllowed()) {
- DBTasks.refreshAllFeeds(getApplicationContext(), false);
- } else {
+ if (!NetworkUtils.networkAvailable() || !NetworkUtils.isFeedRefreshAllowed()) {
Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
+ return Result.retry();
}
- if (!isRunOnce && UserPreferences.isAutoUpdateTimeOfDay()) {
- // WorkManager does not allow to set specific time for repeated tasks.
- // We repeatedly schedule a OneTimeWorkRequest instead.
- AutoUpdateManager.restartUpdateAlarm(getApplicationContext());
+ List<Feed> toUpdate;
+ long feedId = getInputData().getLong(FeedUpdateManager.EXTRA_FEED_ID, -1);
+ if (feedId == -1) { // Update all
+ toUpdate = DBReader.getFeedList();
+ Iterator<Feed> itr = toUpdate.iterator();
+ while (itr.hasNext()) {
+ Feed feed = itr.next();
+ if (!feed.getPreferences().getKeepUpdated()) {
+ itr.remove();
+ }
+ }
+ Collections.shuffle(toUpdate); // If the worker gets cancelled early, every feed has a chance to be updated
+ refreshFeeds(toUpdate, false);
+ } else {
+ toUpdate = new ArrayList<>();
+ Feed feed = DBReader.getFeed(feedId);
+ if (feed == null) {
+ return Result.success();
+ }
+ toUpdate.add(feed);
+ refreshFeeds(toUpdate, true);
}
-
return Result.success();
}
+
+ @NonNull
+ private ForegroundInfo createForegroundInfo(List<Feed> toUpdate) {
+ Context context = getApplicationContext();
+ String contentText = context.getResources().getQuantityString(R.plurals.downloads_left,
+ toUpdate.size(), toUpdate.size());
+ String bigText = Stream.of(toUpdate).map(feed -> "• " + feed.getTitle()).collect(Collectors.joining("\n"));
+ Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_DOWNLOADING)
+ .setContentTitle(context.getString(R.string.download_notification_title_feeds))
+ .setContentText(contentText)
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
+ .setSmallIcon(R.drawable.ic_notification_sync)
+ .setOngoing(true)
+ .addAction(R.drawable.ic_cancel, context.getString(R.string.cancel_label),
+ WorkManager.getInstance(context).createCancelPendingIntent(getId()))
+ .build();
+ return new ForegroundInfo(R.id.notification_updating_feeds, notification);
+ }
+
+ private void refreshFeeds(List<Feed> toUpdate, boolean force) {
+ while (!toUpdate.isEmpty()) {
+ if (isStopped()) {
+ return;
+ }
+ setForegroundAsync(createForegroundInfo(toUpdate));
+ Feed feed = toUpdate.get(0);
+ try {
+ if (feed.isLocalFeed()) {
+ LocalFeedUpdater.updateFeed(feed, getApplicationContext(), null);
+ } else {
+ refreshFeed(feed, force);
+ }
+ } catch (Exception e) {
+ DBWriter.setFeedLastUpdateFailed(feed.getId(), true);
+ DownloadStatus status = new DownloadStatus(feed, feed.getTitle(),
+ DownloadError.ERROR_IO_ERROR, false, e.getMessage(), true);
+ DBWriter.addDownloadStatus(status);
+ }
+ toUpdate.remove(0);
+ }
+ }
+
+ void refreshFeed(Feed feed, boolean force) throws Exception {
+ boolean nextPage = getInputData().getBoolean(FeedUpdateManager.EXTRA_NEXT_PAGE, false)
+ && feed.getNextPageLink() != null;
+ if (nextPage) {
+ feed.setPageNr(feed.getPageNr() + 1);
+ }
+ DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
+ builder.setForce(force || feed.hasLastUpdateFailed());
+ if (nextPage) {
+ builder.setSource(feed.getNextPageLink());
+ }
+ DownloadRequest request = builder.build();
+
+ Downloader downloader = new DefaultDownloaderFactory().create(request);
+ if (downloader == null) {
+ throw new Exception("Unable to create downloader");
+ }
+
+ downloader.call();
+
+ if (!downloader.getResult().isSuccessful()) {
+ if (downloader.getResult().isCancelled()) {
+ return;
+ }
+ DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
+ DBWriter.addDownloadStatus(downloader.getResult());
+ return;
+ }
+
+ FeedSyncTask feedSyncTask = new FeedSyncTask(getApplicationContext(), request);
+ boolean success = feedSyncTask.run();
+
+ if (!success) {
+ DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
+ DBWriter.addDownloadStatus(feedSyncTask.getDownloadStatus());
+ return;
+ }
+
+ if (request.getFeedfileId() == 0) {
+ return; // No download logs for new subscriptions
+ }
+ // we create a 'successful' download log if the feed's last refresh failed
+ List<DownloadStatus> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
+ if (log.size() > 0 && !log.get(0).isSuccessful()) {
+ DBWriter.addDownloadStatus(feedSyncTask.getDownloadStatus());
+ }
+ newEpisodesNotification.showIfNeeded(getApplicationContext(), feedSyncTask.getSavedFeed());
+ if (downloader.permanentRedirectUrl != null) {
+ DBWriter.updateFeedDownloadURL(request.getSource(), downloader.permanentRedirectUrl);
+ } else if (feedSyncTask.getRedirectUrl() != null) {
+ DBWriter.updateFeedDownloadURL(request.getSource(), feedSyncTask.getRedirectUrl());
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 0e55e9a36..9c238137e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -10,19 +10,29 @@ import android.content.IntentFilter;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.ServiceCompat;
-
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
+import de.danoeh.antennapod.core.event.DownloadEvent;
+import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
+import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
+import de.danoeh.antennapod.core.service.download.handler.PostDownloaderTask;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithmFactory;
+import de.danoeh.antennapod.core.util.download.ConnectionStateMonitor;
+import de.danoeh.antennapod.event.FeedItemEvent;
+import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.apache.commons.io.FileUtils;
import org.greenrobot.eventbus.EventBus;
@@ -39,22 +49,6 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.event.DownloadEvent;
-import de.danoeh.antennapod.core.util.download.ConnectionStateMonitor;
-import de.danoeh.antennapod.event.FeedItemEvent;
-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.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
-import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
-import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
-import de.danoeh.antennapod.core.service.download.handler.PostDownloaderTask;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.model.download.DownloadError;
-
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued via the startService intent.
* The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUESTS field of
@@ -69,7 +63,6 @@ public class DownloadService extends Service {
public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.core.service.cancelAll";
public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
public static final String EXTRA_REQUESTS = "downloadRequests";
- public static final String EXTRA_REFRESH_ALL = "refreshAll";
public static final String EXTRA_INITIATED_BY_USER = "initiatedByUser";
public static final String EXTRA_CLEANUP_MEDIA = "cleanupMedia";
@@ -85,7 +78,6 @@ public class DownloadService extends Service {
private final List<DownloadStatus> reportQueue = new ArrayList<>();
private final List<DownloadRequest> failedRequestsForReport = new ArrayList<>();
private DownloadServiceNotification notificationManager;
- private final NewEpisodesNotification newEpisodesNotification;
private NotificationUpdater notificationUpdater;
private ScheduledFuture<?> notificationUpdaterFuture;
private ScheduledFuture<?> downloadPostFuture;
@@ -99,16 +91,12 @@ public class DownloadService extends Service {
}
public DownloadService() {
- newEpisodesNotification = new NewEpisodesNotification();
downloadEnqueueExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "EnqueueThread");
t.setPriority(Thread.MIN_PRIORITY);
return t;
});
- // Must be the first runnable in syncExecutor
- downloadEnqueueExecutor.execute(newEpisodesNotification::loadCountersBeforeRefresh);
-
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
downloadHandleExecutor = Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
r -> {
@@ -140,18 +128,6 @@ public class DownloadService extends Service {
connectionMonitor.enable(getApplicationContext());
}
- public static boolean isDownloadingFeeds() {
- if (!isRunning) {
- return false;
- }
- for (Downloader downloader : downloads) {
- if (downloader.request.getFeedfileType() == Feed.FEEDFILETYPE_FEED && !downloader.cancelled) {
- return true;
- }
- }
- return false;
- }
-
public static boolean isDownloadingFile(String downloadUrl) {
if (!isRunning) {
return false;
@@ -182,13 +158,6 @@ public class DownloadService extends Service {
NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report);
setupNotificationUpdaterIfNecessary();
downloadEnqueueExecutor.execute(() -> onDownloadQueued(intent));
- } else if (intent != null && intent.getBooleanExtra(EXTRA_REFRESH_ALL, false)) {
- Notification notification = notificationManager.updateNotifications(downloads);
- startForeground(R.id.notification_downloading, notification);
- NotificationManagerCompat.from(this).cancel(R.id.notification_download_report);
- NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report);
- setupNotificationUpdaterIfNecessary();
- downloadEnqueueExecutor.execute(() -> enqueueAll(intent));
} else if (downloads.size() == 0) {
shutdown();
} else {
@@ -251,61 +220,12 @@ public class DownloadService extends Service {
});
}
- /**
- * This method MUST NOT, in any case, throw an exception.
- * Otherwise, it hangs up the refresh thread pool.
- */
- private void performLocalFeedRefresh(Downloader downloader, DownloadRequest request) {
- try {
- Feed feed = DBReader.getFeed(request.getFeedfileId());
- LocalFeedUpdater.updateFeed(feed, DownloadService.this, (scanned, totalFiles) -> {
- request.setSize(totalFiles);
- request.setSoFar(scanned);
- request.setProgressPercent((int) (100.0 * scanned / totalFiles));
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- downloadEnqueueExecutor.submit(() -> {
- downloads.remove(downloader);
- stopServiceIfEverythingDone();
- });
- }
-
-
private void handleSuccessfulDownload(Downloader downloader) {
DownloadRequest request = downloader.getDownloadRequest();
DownloadStatus status = downloader.getResult();
final int type = status.getFeedfileType();
- if (type == Feed.FEEDFILETYPE_FEED) {
- Log.d(TAG, "Handling completed Feed Download");
- FeedSyncTask feedSyncTask = new FeedSyncTask(DownloadService.this, request);
- boolean success = feedSyncTask.run();
-
- if (success) {
- if (request.getFeedfileId() == 0) {
- return; // No download logs for new subscriptions
- }
- // we create a 'successful' download log if the feed's last refresh failed
- List<DownloadStatus> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
- if (log.size() > 0 && !log.get(0).isSuccessful()) {
- saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest());
- }
- if (!request.isInitiatedByUser()) {
- // Was stored in the database before and not initiated manually
- newEpisodesNotification.showIfNeeded(DownloadService.this, feedSyncTask.getSavedFeed());
- }
- if (downloader.permanentRedirectUrl != null) {
- DBWriter.updateFeedDownloadURL(request.getSource(), downloader.permanentRedirectUrl);
- } else if (feedSyncTask.getRedirectUrl() != null) {
- DBWriter.updateFeedDownloadURL(request.getSource(), feedSyncTask.getRedirectUrl());
- }
- } else {
- DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
- saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest());
- }
- } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
Log.d(TAG, "Handling completed FeedMedia Download");
MediaDownloadedHandler handler = new MediaDownloadedHandler(DownloadService.this, status, request);
handler.run();
@@ -461,23 +381,6 @@ public class DownloadService extends Service {
}
}
- private void enqueueAll(Intent intent) {
- boolean initiatedByUser = intent.getBooleanExtra(EXTRA_INITIATED_BY_USER, false);
- List<Feed> feeds = DBReader.getFeedList();
- for (Feed feed : feeds) {
- if (feed.getPreferences().getKeepUpdated()) {
- DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
- builder.withInitiatedByUser(initiatedByUser);
- if (feed.hasLastUpdateFailed()) {
- builder.setForce(true);
- }
- addNewRequest(builder.build());
- }
- }
- postDownloaders();
- stopServiceIfEverythingDone();
- }
-
private void addNewRequest(@NonNull DownloadRequest request) {
if (isDownloadingFile(request.getSource())) {
Log.d(TAG, "Skipped enqueueing request. Already running.");
@@ -487,17 +390,11 @@ public class DownloadService extends Service {
return;
}
Log.d(TAG, "Add new request: " + request.getSource());
- if (request.getSource().startsWith(Feed.PREFIX_LOCAL_FOLDER)) {
- Downloader downloader = new LocalFeedStubDownloader(request);
+ writeFileUrl(request);
+ Downloader downloader = downloaderFactory.create(request);
+ if (downloader != null) {
downloads.add(downloader);
- downloadHandleExecutor.submit(() -> performLocalFeedRefresh(downloader, request));
- } else {
- writeFileUrl(request);
- Downloader downloader = downloaderFactory.create(request);
- if (downloader != null) {
- downloads.add(downloader);
- downloadHandleExecutor.submit(() -> performDownload(downloader));
- }
+ downloadHandleExecutor.submit(() -> performDownload(downloader));
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
index 7b7e52e0e..976d8255f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
@@ -5,6 +5,7 @@ import android.content.Intent;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
@@ -49,10 +50,7 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface {
}
public void refreshAllFeeds(Context context, boolean initiatedByUser) {
- Intent launchIntent = new Intent(context, DownloadService.class);
- launchIntent.putExtra(DownloadService.EXTRA_REFRESH_ALL, true);
- launchIntent.putExtra(DownloadService.EXTRA_INITIATED_BY_USER, initiatedByUser);
- ContextCompat.startForegroundService(context, launchIntent);
+ FeedUpdateManager.runOnce(context);
}
public void cancel(Context context, String url) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java
index e3010fe24..9cb1166b4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java
@@ -1,23 +1,20 @@
package de.danoeh.antennapod.core.service.download.handler;
import android.content.Context;
-
import androidx.annotation.NonNull;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.model.download.DownloadStatus;
-import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
public class FeedSyncTask {
- private final DownloadRequest request;
private final Context context;
private Feed savedFeed;
private final FeedParserTask task;
private FeedHandlerResult feedHandlerResult;
public FeedSyncTask(Context context, DownloadRequest request) {
- this.request = request;
this.context = context;
this.task = new FeedParserTask(request);
}
@@ -29,13 +26,6 @@ public class FeedSyncTask {
}
savedFeed = DBTasks.updateFeed(context, feedHandlerResult.feed, false);
- // If loadAllPages=true, check if another page is available and queue it for download
- final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequest.REQUEST_ARG_LOAD_ALL_PAGES);
- final Feed feed = feedHandlerResult.feed;
- if (loadAllPages && feed.getNextPageLink() != null) {
- feed.setId(savedFeed.getId());
- DBTasks.loadNextPageOfFeed(context, feed, true);
- }
return true;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index 18f157908..8b79d594c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -1,20 +1,28 @@
package de.danoeh.antennapod.core.storage;
-import static android.content.Context.MODE_PRIVATE;
-
import android.content.Context;
-import android.content.SharedPreferences;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
-
import androidx.annotation.VisibleForTesting;
-
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
+import de.danoeh.antennapod.core.util.LongList;
+import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
+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.DownloadStatus;
+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.PodDBAdapter;
import de.danoeh.antennapod.storage.database.mapper.FeedCursorMapper;
+import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
@@ -29,31 +37,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.event.FeedItemEvent;
-import de.danoeh.antennapod.event.FeedListUpdateEvent;
-import de.danoeh.antennapod.event.MessageEvent;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.model.download.DownloadStatus;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
-import de.danoeh.antennapod.model.download.DownloadError;
-import de.danoeh.antennapod.core.util.LongList;
-import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
-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;
-
/**
* Provides methods for doing common tasks that use DBReader and DBWriter.
*/
public final class DBTasks {
private static final String TAG = "DBTasks";
- private static final String PREF_NAME = "dbtasks";
- private static final String PREF_LAST_REFRESH = "last_refresh";
-
/**
* Executor service used by the autodownloadUndownloadedEpisodes method.
*/
@@ -104,68 +93,12 @@ public final class DBTasks {
}
}
- /**
- * Refreshes all feeds.
- * It must not be from the main thread.
- * This method might ignore subsequent calls if it is still
- * enqueuing Feeds for download from a previous call
- *
- * @param context Might be used for accessing the database
- * @param initiatedByUser a boolean indicating if the refresh was triggered by user action.
- */
- public static void refreshAllFeeds(final Context context, boolean initiatedByUser) {
- DownloadServiceInterface.get().refreshAllFeeds(context, initiatedByUser);
-
- SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
- prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
-
- SynchronizationQueueSink.syncNow();
- // Note: automatic download of episodes will be done but not here.
- // Instead it is done after all feeds have been refreshed (asynchronously),
- // in DownloadService.onDestroy()
- // See Issue #2577 for the details of the rationale
- }
-
-
-
- /**
- * Queues the next page of this Feed for download. The given Feed has to be a paged
- * Feed (isPaged()=true) and must contain a nextPageLink.
- *
- * @param context Used for requesting the download.
- * @param feed The feed whose next page should be loaded.
- * @param loadAllPages True if any subsequent pages should also be loaded, false otherwise.
- */
- public static void loadNextPageOfFeed(final Context context, Feed feed, boolean loadAllPages) {
- if (feed.isPaged() && feed.getNextPageLink() != null) {
- int pageNr = feed.getPageNr() + 1;
- Feed nextFeed = new Feed(feed.getNextPageLink(), null, feed.getTitle() + "(" + pageNr + ")");
- nextFeed.setPageNr(pageNr);
- nextFeed.setPaged(true);
- nextFeed.setId(feed.getId());
-
- DownloadRequest.Builder builder = DownloadRequestCreator.create(nextFeed);
- builder.loadAllPages(loadAllPages);
- DownloadServiceInterface.get().download(context, false, builder.build());
- } else {
- Log.e(TAG, "loadNextPageOfFeed: Feed was either not paged or contained no nextPageLink");
- }
- }
-
public static void forceRefreshFeed(Context context, Feed feed, boolean initiatedByUser) {
forceRefreshFeed(context, feed, false, initiatedByUser);
}
- public static void forceRefreshCompleteFeed(final Context context, final Feed feed) {
- forceRefreshFeed(context, feed, true, true);
- }
-
private static void forceRefreshFeed(Context context, Feed feed, boolean loadAllPages, boolean initiatedByUser) {
- DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
- builder.withInitiatedByUser(initiatedByUser);
- builder.setForce(true);
- builder.loadAllPages(loadAllPages);
- DownloadServiceInterface.get().download(context, false, builder.build());
+ FeedUpdateManager.runOnce(context, feed);
}
/**
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 9b4146f15..dcee8a45a 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
@@ -665,6 +665,15 @@ public class DBWriter {
adapter.close();
}
+ public static Future<?> resetPagedFeedPage(Feed feed) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.resetPagedFeedPage(feed);
+ adapter.close();
+ });
+ }
+
/*
* Sets the 'read'-attribute of all specified FeedItems
*
@@ -698,7 +707,6 @@ public class DBWriter {
});
}
-
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
*
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 73f467154..2fd492cbd 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
@@ -20,16 +20,15 @@ import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
+import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.core.service.download.DownloadService;
-import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -153,9 +152,10 @@ public class SyncService extends Worker {
continue;
}
if (!UrlChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) {
- Feed feed = new Feed(downloadUrl, null);
- DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
- DownloadServiceInterface.get().download(getApplicationContext(), false, builder.build());
+ Feed feed = new Feed(downloadUrl, null, "Unknown podcast");
+ feed.setItems(Collections.emptyList());
+ Feed newFeed = DBTasks.updateFeed(getApplicationContext(), feed, false);
+ FeedUpdateManager.runOnce(getApplicationContext(), newFeed);
}
}
@@ -193,9 +193,13 @@ public class SyncService extends Worker {
private void waitForDownloadServiceCompleted() {
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_wait_for_downloads));
try {
- while (DownloadService.isRunning) {
+ while (true) {
//noinspection BusyWait
Thread.sleep(1000);
+ FeedUpdateRunningEvent event = EventBus.getDefault().getStickyEvent(FeedUpdateRunningEvent.class);
+ if (event == null || !event.isFeedUpdateRunning) {
+ return;
+ }
}
} catch (InterruptedException e) {
e.printStackTrace();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
deleted file mode 100644
index 0602fc4fe..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package de.danoeh.antennapod.core.util.download;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import androidx.work.Constraints;
-import androidx.work.Data;
-import androidx.work.ExistingPeriodicWorkPolicy;
-import androidx.work.ExistingWorkPolicy;
-import androidx.work.NetworkType;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.PeriodicWorkRequest;
-import androidx.work.WorkManager;
-
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.concurrent.TimeUnit;
-
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.FeedUpdateWorker;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.util.NetworkUtils;
-
-public class AutoUpdateManager {
- private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker";
- private static final String WORK_ID_FEED_UPDATE_ONCE = WORK_ID_FEED_UPDATE + "Once";
- private static final String TAG = "AutoUpdateManager";
-
- private AutoUpdateManager() {
-
- }
-
- /**
- * Start / restart periodic auto feed refresh
- * @param context Context
- */
- public static void restartUpdateAlarm(Context context) {
- if (UserPreferences.isAutoUpdateDisabled()) {
- disableAutoUpdate(context);
- } else if (UserPreferences.isAutoUpdateTimeOfDay()) {
- int[] timeOfDay = UserPreferences.getUpdateTimeOfDay();
- Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
- restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1], context);
- } else {
- long milliseconds = UserPreferences.getUpdateInterval();
- restartUpdateIntervalAlarm(milliseconds, context);
- }
- }
-
- /**
- * Sets the interval in which the feeds are refreshed automatically
- */
- private static void restartUpdateIntervalAlarm(long intervalMillis, Context context) {
- Log.d(TAG, "Restarting update alarm.");
-
- PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(FeedUpdateWorker.class,
- intervalMillis, TimeUnit.MILLISECONDS)
- .setConstraints(getConstraints())
- .build();
-
- WorkManager.getInstance(context).enqueueUniquePeriodicWork(
- WORK_ID_FEED_UPDATE, ExistingPeriodicWorkPolicy.REPLACE, workRequest);
- }
-
- /**
- * Sets time of day the feeds are refreshed automatically
- */
- private static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute, Context context) {
- Log.d(TAG, "Restarting update alarm.");
-
- Calendar now = Calendar.getInstance();
- Calendar alarm = (Calendar) now.clone();
- alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
- alarm.set(Calendar.MINUTE, minute);
- if (alarm.before(now) || alarm.equals(now)) {
- alarm.add(Calendar.DATE, 1);
- }
- long triggerAtMillis = alarm.getTimeInMillis() - now.getTimeInMillis();
-
- OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
- .setConstraints(getConstraints())
- .setInitialDelay(triggerAtMillis, TimeUnit.MILLISECONDS)
- .build();
-
- WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE,
- ExistingWorkPolicy.REPLACE, workRequest);
- }
-
- /**
- * Run auto feed refresh once in background, as soon as what OS scheduling allows.
- *
- * Callers from UI should use {@link #runImmediate(Context)}, as it will guarantee
- * the refresh be run immediately.
- * @param context Context
- */
- public static void runOnce(Context context) {
- Log.d(TAG, "Run auto update once, as soon as OS allows.");
-
- OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
- .setConstraints(getConstraints())
- .setInitialDelay(0L, TimeUnit.MILLISECONDS)
- .setInputData(new Data.Builder()
- .putBoolean(FeedUpdateWorker.PARAM_RUN_ONCE, true)
- .build()
- )
- .build();
-
- WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE_ONCE,
- ExistingWorkPolicy.REPLACE, workRequest);
-
- }
-
- /**
- /**
- * Run auto feed refresh once in background immediately, using its own thread.
- *
- * Callers where the additional threads is not suitable should use {@link #runOnce(Context)}
- */
- public static void runImmediate(@NonNull Context context) {
- Log.d(TAG, "Run auto update immediately in background.");
- if (!NetworkUtils.networkAvailable()) {
- Log.d(TAG, "Ignoring: No network connection.");
- } else if (NetworkUtils.isFeedRefreshAllowed()) {
- startRefreshAllFeeds(context);
- } else {
- confirmMobileAllFeedsRefresh(context);
- }
- }
-
- private static void confirmMobileAllFeedsRefresh(final Context context) {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
- .setTitle(R.string.feed_refresh_title)
- .setMessage(R.string.confirm_mobile_feed_refresh_dialog_message)
- .setPositiveButton(R.string.confirm_mobile_streaming_button_once,
- (dialog, which) -> startRefreshAllFeeds(context))
- .setNeutralButton(R.string.confirm_mobile_streaming_button_always, (dialog, which) -> {
- UserPreferences.setAllowMobileFeedRefresh(true);
- startRefreshAllFeeds(context);
- })
- .setNegativeButton(R.string.no, null);
- builder.show();
- }
-
- private static void startRefreshAllFeeds(final Context context) {
- new Thread(() -> DBTasks.refreshAllFeeds(
- context.getApplicationContext(), true), "ManualRefreshAllFeeds").start();
- }
-
- public static void disableAutoUpdate(Context context) {
- WorkManager.getInstance(context).cancelUniqueWork(WORK_ID_FEED_UPDATE);
- }
-
- private static Constraints getConstraints() {
- Constraints.Builder constraints = new Constraints.Builder();
-
- if (UserPreferences.isAllowMobileFeedRefresh()) {
- constraints.setRequiredNetworkType(NetworkType.CONNECTED);
- } else {
- constraints.setRequiredNetworkType(NetworkType.UNMETERED);
- }
- return constraints.build();
- }
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java
new file mode 100644
index 000000000..d1a273d4e
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java
@@ -0,0 +1,112 @@
+package de.danoeh.antennapod.core.util.download;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.work.Constraints;
+import androidx.work.Data;
+import androidx.work.ExistingPeriodicWorkPolicy;
+import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.OutOfQuotaPolicy;
+import androidx.work.PeriodicWorkRequest;
+import androidx.work.WorkManager;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.service.FeedUpdateWorker;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.storage.preferences.UserPreferences;
+
+import java.util.concurrent.TimeUnit;
+
+public class FeedUpdateManager {
+ public static final String WORK_TAG_FEED_UPDATE = "feedUpdate";
+ private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker";
+ private static final String WORK_ID_FEED_UPDATE_MANUAL = "feedUpdateManual";
+ public static final String EXTRA_FEED_ID = "feed_id";
+ public static final String EXTRA_NEXT_PAGE = "next_page";
+ private static final String TAG = "AutoUpdateManager";
+
+ private FeedUpdateManager() {
+
+ }
+
+ /**
+ * Start / restart periodic auto feed refresh
+ * @param context Context
+ */
+ public static void restartUpdateAlarm(Context context, boolean replace) {
+ if (UserPreferences.isAutoUpdateDisabled()) {
+ WorkManager.getInstance(context).cancelUniqueWork(WORK_ID_FEED_UPDATE);
+ } else {
+ PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
+ FeedUpdateWorker.class, UserPreferences.getUpdateInterval(), TimeUnit.HOURS)
+ .setConstraints(getConstraints())
+ .build();
+ WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_ID_FEED_UPDATE,
+ replace ? ExistingPeriodicWorkPolicy.REPLACE : ExistingPeriodicWorkPolicy.KEEP, workRequest);
+ }
+ }
+
+ public static void runOnce(Context context) {
+ runOnce(context, null, false);
+ }
+
+ public static void runOnce(Context context, Feed feed) {
+ runOnce(context, feed, false);
+ }
+
+ public static void runOnce(Context context, Feed feed, boolean nextPage) {
+ OneTimeWorkRequest.Builder workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
+ .setInitialDelay(0L, TimeUnit.MILLISECONDS)
+ .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
+ .addTag(WORK_TAG_FEED_UPDATE);
+ if (feed != null) {
+ Data.Builder builder = new Data.Builder();
+ builder.putLong(EXTRA_FEED_ID, feed.getId());
+ builder.putBoolean(EXTRA_NEXT_PAGE, nextPage);
+ workRequest.setInputData(builder.build());
+ }
+ WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE_MANUAL,
+ ExistingWorkPolicy.REPLACE, workRequest.build());
+ }
+
+ public static void runOnceOrAsk(@NonNull Context context) {
+ Log.d(TAG, "Run auto update immediately in background.");
+ if (!NetworkUtils.networkAvailable()) {
+ Log.d(TAG, "Ignoring: No network connection.");
+ } else if (NetworkUtils.isFeedRefreshAllowed()) {
+ runOnce(context);
+ } else {
+ confirmMobileAllFeedsRefresh(context);
+ }
+ }
+
+ private static void confirmMobileAllFeedsRefresh(final Context context) {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
+ .setTitle(R.string.feed_refresh_title)
+ .setMessage(R.string.confirm_mobile_feed_refresh_dialog_message)
+ .setPositiveButton(R.string.confirm_mobile_streaming_button_once,
+ (dialog, which) -> runOnce(context))
+ .setNeutralButton(R.string.confirm_mobile_streaming_button_always, (dialog, which) -> {
+ UserPreferences.setAllowMobileFeedRefresh(true);
+ runOnce(context);
+ })
+ .setNegativeButton(R.string.no, null);
+ builder.show();
+ }
+
+ private static Constraints getConstraints() {
+ Constraints.Builder constraints = new Constraints.Builder();
+
+ if (UserPreferences.isAllowMobileFeedRefresh()) {
+ constraints.setRequiredNetworkType(NetworkType.CONNECTED);
+ } else {
+ constraints.setRequiredNetworkType(NetworkType.UNMETERED);
+ }
+ return constraints.build();
+ }
+
+}
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 39f62a5d7..f3c0a0a3c 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -25,6 +25,28 @@
<item>heavy</item>
</string-array>
+ <string-array name="feed_refresh_interval_entries">
+ <item>@string/feed_refresh_never</item>
+ <item>@string/feed_every_hour</item>
+ <item>@string/feed_every_2_hours</item>
+ <item>@string/feed_every_4_hours</item>
+ <item>@string/feed_every_8_hours</item>
+ <item>@string/feed_every_12_hours</item>
+ <item>@string/feed_every_24_hours</item>
+ <item>@string/feed_every_72_hours</item>
+ </string-array>
+
+ <string-array name="feed_refresh_interval_values">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>4</item>
+ <item>8</item>
+ <item>12</item>
+ <item>24</item>
+ <item>72</item>
+ </string-array>
+
<string-array name="globalNewEpisodesActionItems">
<item>@string/feed_new_episodes_action_add_to_inbox</item>
<item>@string/feed_new_episodes_action_nothing</item>
diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml
index 87046cc0f..90d143d38 100644
--- a/core/src/main/res/values/ids.xml
+++ b/core/src/main/res/values/ids.xml
@@ -19,6 +19,7 @@
<item name="notification_gpodnet_sync_error" type="id"/>
<item name="notification_gpodnet_sync_autherror" type="id"/>
<item name="notification_downloading" type="id"/>
+ <item name="notification_updating_feeds" type="id"/>
<item name="notification_download_report" type="id"/>
<item name="notification_auto_download_report" type="id"/>
<item name="notification_playing" type="id"/>