summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/src')
-rw-r--r--core/src/main/AndroidManifest.xml4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java35
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java58
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequestCreator.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java536
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java102
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java306
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java265
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/LocalFeedStubDownloader.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/PostDownloaderTask.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadResultComparator.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java15
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java59
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java95
29 files changed, 409 insertions, 1349 deletions
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 6f5508f27..e186a856f 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -16,10 +16,6 @@
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
- <service
- android:name=".service.download.DownloadService"
- android:enabled="true" />
-
<service android:name=".service.playback.PlaybackService"
android:label="@string/app_name"
android:enabled="true"
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
deleted file mode 100644
index efd53ab9d..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.danoeh.antennapod.core.event;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import de.danoeh.antennapod.core.service.download.Downloader;
-
-public class DownloadEvent {
-
- public final DownloaderUpdate update;
-
- private DownloadEvent(DownloaderUpdate downloader) {
- this.update = downloader;
- }
-
- public static DownloadEvent refresh(List<Downloader> list) {
- list = new ArrayList<>(list);
- DownloaderUpdate update = new DownloaderUpdate(list);
- return new DownloadEvent(update);
- }
-
- @NonNull
- @Override
- public String toString() {
- return "DownloadEvent{" +
- "update=" + update +
- '}';
- }
-
- public boolean hasChangedFeedUpdateStatus(boolean oldStatus) {
- return oldStatus != update.feedIds.length > 0;
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
deleted file mode 100644
index 1cab7e0f0..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package de.danoeh.antennapod.core.event;
-
-import androidx.annotation.NonNull;
-
-import java.util.Arrays;
-import java.util.List;
-
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.core.service.download.Downloader;
-import de.danoeh.antennapod.core.util.LongList;
-
-public class DownloaderUpdate {
-
- /* Downloaders that are currently running */
- @NonNull
- public final List<Downloader> downloaders;
-
- /**
- * IDs of feeds that are currently being downloaded
- * Often used to show some progress wheel in the action bar
- */
- public final long[] feedIds;
-
- /**
- * IDs of feed media that are currently being downloaded
- * Can be used to show and update download progress bars
- */
- public final long[] mediaIds;
-
- DownloaderUpdate(@NonNull List<Downloader> downloaders) {
- this.downloaders = downloaders;
- LongList feedIds1 = new LongList();
- LongList mediaIds1 = new LongList();
- for(Downloader d1 : downloaders) {
- int type = d1.getDownloadRequest().getFeedfileType();
- long id = d1.getDownloadRequest().getFeedfileId();
- if(type == Feed.FEEDFILETYPE_FEED) {
- feedIds1.add(id);
- } else if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- mediaIds1.add(id);
- }
- }
-
- this.feedIds = feedIds1.toArray();
- this.mediaIds = mediaIds1.toArray();
- }
-
- @NonNull
- @Override
- public String toString() {
- return "DownloaderUpdate{" +
- "downloaders=" + downloaders +
- ", feedIds=" + Arrays.toString(feedIds) +
- ", mediaIds=" + Arrays.toString(mediaIds) +
- '}';
- }
-}
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 d4d948b2a..03881ee4f 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
@@ -28,7 +28,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.documentfile.provider.DocumentFile;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.util.FastDocumentFile;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -246,8 +246,8 @@ public class LocalFeedUpdater {
}
private static void reportError(Feed feed, String reasonDetailed) {
- DownloadStatus status = new DownloadStatus(feed, feed.getTitle(),
- DownloadError.ERROR_IO_ERROR, false, reasonDetailed, true);
+ DownloadResult status = new DownloadResult(feed, feed.getTitle(),
+ DownloadError.ERROR_IO_ERROR, false, reasonDetailed);
DBWriter.addDownloadStatus(status);
DBWriter.setFeedLastUpdateFailed(feed.getId(), true);
}
@@ -256,8 +256,7 @@ public class LocalFeedUpdater {
* Reports a successful download status.
*/
private static void reportSuccess(Feed feed) {
- DownloadStatus status = new DownloadStatus(feed, feed.getTitle(),
- DownloadError.SUCCESS, true, null, true);
+ DownloadResult status = new DownloadResult(feed, feed.getTitle(), DownloadError.SUCCESS, true, null);
DBWriter.addDownloadStatus(status);
DBWriter.setFeedLastUpdateFailed(feed.getId(), false);
}
@@ -266,21 +265,21 @@ public class LocalFeedUpdater {
* Answers if reporting success is needed for the given feed.
*/
private static boolean mustReportDownloadSuccessful(Feed feed) {
- List<DownloadStatus> downloadStatuses = DBReader.getFeedDownloadLog(feed.getId());
+ List<DownloadResult> downloadResults = DBReader.getFeedDownloadLog(feed.getId());
- if (downloadStatuses.isEmpty()) {
+ if (downloadResults.isEmpty()) {
// report success if never reported before
return true;
}
- Collections.sort(downloadStatuses, (downloadStatus1, downloadStatus2) ->
+ Collections.sort(downloadResults, (downloadStatus1, downloadStatus2) ->
downloadStatus1.getCompletionDate().compareTo(downloadStatus2.getCompletionDate()));
- DownloadStatus lastDownloadStatus = downloadStatuses.get(downloadStatuses.size() - 1);
+ DownloadResult lastDownloadResult = downloadResults.get(downloadResults.size() - 1);
// report success if the last update was not successful
// (avoid logging success again if the last update was ok)
- return !lastDownloadStatus.isSuccessful();
+ return !lastDownloadResult.isSuccessful();
}
@FunctionalInterface
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 8d9f046e2..5f59f0c41 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
@@ -20,12 +20,13 @@ 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.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.NetworkUtils;
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.download.DownloadResult;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
@@ -80,6 +81,7 @@ public class FeedUpdateWorker extends Worker {
refreshFeeds(toUpdate, true);
}
notificationManager.cancel(R.id.notification_updating_feeds);
+ DBTasks.autodownloadUndownloadedItems(getApplicationContext());
return Result.success();
}
@@ -115,8 +117,8 @@ public class FeedUpdateWorker extends Worker {
}
} catch (Exception e) {
DBWriter.setFeedLastUpdateFailed(feed.getId(), true);
- DownloadStatus status = new DownloadStatus(feed, feed.getTitle(),
- DownloadError.ERROR_IO_ERROR, false, e.getMessage(), true);
+ DownloadResult status = new DownloadResult(feed, feed.getTitle(),
+ DownloadError.ERROR_IO_ERROR, false, e.getMessage());
DBWriter.addDownloadStatus(status);
}
toUpdate.remove(0);
@@ -144,7 +146,7 @@ public class FeedUpdateWorker extends Worker {
downloader.call();
if (!downloader.getResult().isSuccessful()) {
- if (downloader.getResult().isCancelled()) {
+ if (downloader.cancelled) {
return;
}
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
@@ -165,7 +167,7 @@ public class FeedUpdateWorker extends Worker {
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());
+ List<DownloadResult> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
if (log.size() > 0 && !log.get(0).isSuccessful()) {
DBWriter.addDownloadStatus(feedSyncTask.getDownloadStatus());
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequestCreator.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequestCreator.java
index d6a4b8378..5ca904ff6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequestCreator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequestCreator.java
@@ -21,9 +21,6 @@ public class DownloadRequestCreator {
public static DownloadRequest.Builder create(Feed feed) {
File dest = new File(getFeedfilePath(), getFeedfileName(feed));
- if (!isFilenameAvailable(dest.toString()) && !feed.isLocalFeed()) {
- dest = findUnusedFile(dest);
- }
Log.d(TAG, "Requesting download of url " + feed.getDownload_url());
String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null;
@@ -45,7 +42,7 @@ public class DownloadRequestCreator {
dest = new File(getMediafilePath(media), getMediafilename(media));
}
- if (!isFilenameAvailable(dest.toString()) || (!partiallyDownloadedFileExists && dest.exists())) {
+ if (dest.exists() && !partiallyDownloadedFileExists) {
dest = findUnusedFile(dest);
}
Log.d(TAG, "Requesting download of url " + media.getDownload_url());
@@ -72,7 +69,7 @@ public class DownloadRequestCreator {
+ FilenameUtils.getExtension(dest.getName());
Log.d(TAG, "Testing filename " + newName);
newDest = new File(dest.getParent(), newName);
- if (!newDest.exists() && isFilenameAvailable(newDest.toString())) {
+ if (!newDest.exists()) {
Log.d(TAG, "File doesn't exist yet. Using " + newName);
break;
}
@@ -80,19 +77,6 @@ public class DownloadRequestCreator {
return newDest;
}
- /**
- * Returns true if a filename is available and false if it has already been
- * taken by another requested download.
- */
- private static boolean isFilenameAvailable(String path) {
- for (Downloader downloader : DownloadService.downloads) {
- if (downloader.request.getDestination().equals(path)) {
- return false;
- }
- }
- return true;
- }
-
private static String getFeedfilePath() {
return UserPreferences.getDataFolder(FEED_DOWNLOADPATH).toString() + "/";
}
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
deleted file mode 100644
index 9c238137e..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ /dev/null
@@ -1,536 +0,0 @@
-package de.danoeh.antennapod.core.service.download;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-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.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;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 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
- * the intent.
- * After the downloads have finished, the downloaded object will be passed on to a specific handler, depending on the
- * type of the feedfile.
- */
-public class DownloadService extends Service {
- private static final String TAG = "DownloadService";
- private static final int SCHED_EX_POOL_SIZE = 1;
- public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.core.service.cancelDownload";
- 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_INITIATED_BY_USER = "initiatedByUser";
- public static final String EXTRA_CLEANUP_MEDIA = "cleanupMedia";
-
- public static boolean isRunning = false;
-
- // Can be modified from another thread while iterating. Both possible race conditions are not critical:
- // Remove while iterating: We think it is still downloading and don't start a new download with the same file.
- // Add while iterating: We think it is not downloading and might start a second download with the same file.
- static final List<Downloader> downloads = Collections.synchronizedList(new CopyOnWriteArrayList<>());
- private final ExecutorService downloadHandleExecutor;
- private final ExecutorService downloadEnqueueExecutor;
-
- private final List<DownloadStatus> reportQueue = new ArrayList<>();
- private final List<DownloadRequest> failedRequestsForReport = new ArrayList<>();
- private DownloadServiceNotification notificationManager;
- private NotificationUpdater notificationUpdater;
- private ScheduledFuture<?> notificationUpdaterFuture;
- private ScheduledFuture<?> downloadPostFuture;
- private final ScheduledThreadPoolExecutor notificationUpdateExecutor;
- private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
- private ConnectionStateMonitor connectionMonitor;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- public DownloadService() {
-
- downloadEnqueueExecutor = Executors.newSingleThreadExecutor(r -> {
- Thread t = new Thread(r, "EnqueueThread");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- });
- Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
- downloadHandleExecutor = Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
- r -> {
- Thread t = new Thread(r, "DownloadThread");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- });
- notificationUpdateExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- r -> {
- Thread t = new Thread(r, "NotificationUpdateExecutor");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }, (r, executor) -> Log.w(TAG, "SchedEx rejected submission of new task")
- );
- }
-
- @Override
- public void onCreate() {
- Log.d(TAG, "Service started");
- isRunning = true;
- notificationManager = new DownloadServiceNotification(this);
-
- IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
- registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
-
- connectionMonitor = new ConnectionStateMonitor();
- connectionMonitor.enable(getApplicationContext());
- }
-
- public static boolean isDownloadingFile(String downloadUrl) {
- if (!isRunning) {
- return false;
- }
- for (Downloader downloader : downloads) {
- if (downloader.request.getSource().equals(downloadUrl) && !downloader.cancelled) {
- return true;
- }
- }
- return false;
- }
-
- public static DownloadRequest findRequest(String downloadUrl) {
- for (Downloader downloader : downloads) {
- if (downloader.request.getSource().equals(downloadUrl)) {
- return downloader.request;
- }
- }
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent != null && intent.hasExtra(EXTRA_REQUESTS)) {
- 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(() -> onDownloadQueued(intent));
- } else if (downloads.size() == 0) {
- shutdown();
- } else {
- Log.d(TAG, "onStartCommand: Unknown intent");
- }
- return Service.START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- Log.d(TAG, "Service shutting down");
- isRunning = false;
-
- boolean showAutoDownloadReport = UserPreferences.showAutoDownloadReport();
- if (UserPreferences.showDownloadReport() || showAutoDownloadReport) {
- notificationManager.updateReport(reportQueue, showAutoDownloadReport, failedRequestsForReport);
- reportQueue.clear();
- failedRequestsForReport.clear();
- }
-
- unregisterReceiver(cancelDownloadReceiver);
- connectionMonitor.disable(getApplicationContext());
-
- EventBus.getDefault().postSticky(DownloadEvent.refresh(Collections.emptyList()));
- cancelNotificationUpdater();
- downloadEnqueueExecutor.shutdownNow();
- downloadHandleExecutor.shutdownNow();
- notificationUpdateExecutor.shutdownNow();
- if (downloadPostFuture != null) {
- downloadPostFuture.cancel(true);
- }
- downloads.clear();
-
- // start auto download in case anything new has shown up
- DBTasks.autodownloadUndownloadedItems(getApplicationContext());
- }
-
- /**
- * This method MUST NOT, in any case, throw an exception.
- * Otherwise, it hangs up the refresh thread pool.
- */
- private void performDownload(Downloader downloader) {
- try {
- downloader.call();
- } catch (Exception e) {
- e.printStackTrace();
- }
- try {
- if (downloader.getResult().isSuccessful()) {
- handleSuccessfulDownload(downloader);
- } else {
- handleFailedDownload(downloader);
- }
- } 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 == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- Log.d(TAG, "Handling completed FeedMedia Download");
- MediaDownloadedHandler handler = new MediaDownloadedHandler(DownloadService.this, status, request);
- handler.run();
- saveDownloadStatus(handler.getUpdatedStatus(), downloader.getDownloadRequest());
- }
- }
-
- private void handleFailedDownload(Downloader downloader) {
- DownloadStatus status = downloader.getResult();
- final int type = status.getFeedfileType();
-
- if (!status.isCancelled()) {
- if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
- notificationManager.postAuthenticationNotification(downloader.getDownloadRequest());
- } else if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
- && Integer.parseInt(status.getReasonDetailed()) == 416) {
-
- Log.d(TAG, "Requested invalid range, restarting download from the beginning");
- FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination()));
- DownloadServiceInterface.get().download(this, false, downloader.getDownloadRequest());
- } else {
- Log.e(TAG, "Download failed");
- saveDownloadStatus(status, downloader.getDownloadRequest());
- new FailedDownloadHandler(downloader.getDownloadRequest()).run();
-
- if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- FeedItem item = getFeedItemFromId(status.getFeedfileId());
- if (item == null) {
- return;
- }
- item.increaseFailedAutoDownloadAttempts(System.currentTimeMillis());
- DBWriter.setFeedItem(item);
- // to make lists reload the failed item, we fake an item update
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- }
- }
- } else {
- // if FeedMedia download has been canceled, fake FeedItem update
- // so that lists reload that it
- if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- FeedItem item = getFeedItemFromId(status.getFeedfileId());
- if (item == null) {
- return;
- }
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- }
- }
- }
-
- private final BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "cancelDownloadReceiver: " + intent.getAction());
- if (!isRunning) {
- return;
- }
- if (TextUtils.equals(intent.getAction(), ACTION_CANCEL_DOWNLOAD)) {
- String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
- if (url == null) {
- throw new IllegalArgumentException("ACTION_CANCEL_DOWNLOAD intent needs download url extra");
- }
- downloadEnqueueExecutor.execute(() -> {
- doCancel(url);
- postDownloaders();
- stopServiceIfEverythingDone();
- });
- } else if (TextUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
- downloadEnqueueExecutor.execute(() -> {
- for (Downloader d : downloads) {
- d.cancel();
- }
- Log.d(TAG, "Cancelled all downloads");
- postDownloaders();
- stopServiceIfEverythingDone();
- });
- }
- }
- };
-
- private void doCancel(String url) {
- Log.d(TAG, "Cancelling download with url " + url);
- for (Downloader downloader : downloads) {
- if (downloader.cancelled || !downloader.getDownloadRequest().getSource().equals(url)) {
- continue;
- }
- downloader.cancel();
- DownloadRequest request = downloader.getDownloadRequest();
- FeedItem item = getFeedItemFromId(request.getFeedfileId());
- if (item != null) {
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- // undo enqueue upon cancel
- if (request.isMediaEnqueued()) {
- Log.v(TAG, "Undoing enqueue upon cancelling download");
- DBWriter.removeQueueItem(getApplicationContext(), false, item);
- }
- }
- }
- }
-
- private void onDownloadQueued(Intent intent) {
- List<DownloadRequest> requests = intent.getParcelableArrayListExtra(EXTRA_REQUESTS);
- if (requests == null) {
- throw new IllegalArgumentException("ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
- }
- Log.d(TAG, "Received enqueue request. #requests=" + requests.size());
-
- if (intent.getBooleanExtra(EXTRA_CLEANUP_MEDIA, false)) {
- EpisodeCleanupAlgorithmFactory.build().makeRoomForEpisodes(getApplicationContext(), requests.size());
- }
-
- for (DownloadRequest request : requests) {
- addNewRequest(request);
- }
- postDownloaders();
- stopServiceIfEverythingDone();
-
- // Add to-download items to the queue before actual download completed
- // so that the resulting queue order is the same as when download is clicked
- enqueueFeedItems(requests);
- }
-
- private void enqueueFeedItems(@NonNull List<DownloadRequest> requests) {
- List<FeedItem> feedItems = new ArrayList<>();
- for (DownloadRequest request : requests) {
- if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- long mediaId = request.getFeedfileId();
- FeedMedia media = DBReader.getFeedMedia(mediaId);
- if (media == null) {
- Log.w(TAG, "enqueueFeedItems() : FeedFile Id " + mediaId + " is not found. ignore it.");
- continue;
- }
- feedItems.add(media.getItem());
- }
- }
- List<FeedItem> actuallyEnqueued = Collections.emptyList();
- try {
- actuallyEnqueued = DBTasks.enqueueFeedItemsToDownload(getApplicationContext(), feedItems);
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
-
- for (DownloadRequest request : requests) {
- if (request.getFeedfileType() != FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- continue;
- }
- final long mediaId = request.getFeedfileId();
- for (FeedItem item : actuallyEnqueued) {
- if (item.getMedia() != null && item.getMedia().getId() == mediaId) {
- request.setMediaEnqueued(true);
- }
- }
- }
- }
-
- private void addNewRequest(@NonNull DownloadRequest request) {
- if (isDownloadingFile(request.getSource())) {
- Log.d(TAG, "Skipped enqueueing request. Already running.");
- return;
- } else if (downloadHandleExecutor.isShutdown()) {
- Log.d(TAG, "Skipped enqueueing request. Service is already shutting down.");
- return;
- }
- Log.d(TAG, "Add new request: " + request.getSource());
- writeFileUrl(request);
- Downloader downloader = downloaderFactory.create(request);
- if (downloader != null) {
- downloads.add(downloader);
- downloadHandleExecutor.submit(() -> performDownload(downloader));
- }
- }
-
- @VisibleForTesting
- public static DownloaderFactory getDownloaderFactory() {
- return downloaderFactory;
- }
-
- // public scope rather than package private,
- // because androidTest put classes in the non-standard de.test.antennapod hierarchy
- @VisibleForTesting
- public static void setDownloaderFactory(DownloaderFactory downloaderFactory) {
- DownloadService.downloaderFactory = downloaderFactory;
- }
-
- /**
- * Adds a new DownloadStatus object to the list of completed downloads and
- * saves it in the database
- *
- * @param status the download that is going to be saved
- */
- private void saveDownloadStatus(@NonNull DownloadStatus status, @NonNull DownloadRequest request) {
- reportQueue.add(status);
- if (!status.isSuccessful() && !status.isCancelled()) {
- failedRequestsForReport.add(request);
- }
- DBWriter.addDownloadStatus(status);
- }
-
- /**
- * Check if there's something else to download, otherwise stop.
- */
- private void stopServiceIfEverythingDone() {
- Log.d(TAG, downloads.size() + " downloads left");
- if (downloads.size() <= 0) {
- Log.d(TAG, "Attempting shutdown");
- shutdown();
- }
- }
-
- @Nullable
- private FeedItem getFeedItemFromId(long id) {
- FeedMedia media = DBReader.getFeedMedia(id);
- if (media != null) {
- return media.getItem();
- } else {
- return null;
- }
- }
-
- /**
- * Creates the destination file and writes FeedMedia File_url directly after starting download
- * to make it possible to resume download after the service was killed by the system.
- */
- private void writeFileUrl(DownloadRequest request) {
- if (request.getFeedfileType() != FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- return;
- }
-
- File dest = new File(request.getDestination());
- if (!dest.exists()) {
- try {
- dest.createNewFile();
- } catch (IOException e) {
- Log.e(TAG, "Unable to create file");
- }
- }
-
- if (dest.exists()) {
- Log.d(TAG, "Writing file url");
- FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
- if (media == null) {
- Log.d(TAG, "No media");
- return;
- }
- media.setFile_url(request.getDestination());
- try {
- DBWriter.setFeedMedia(media).get();
- } catch (InterruptedException e) {
- Log.e(TAG, "writeFileUrl was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in writeFileUrl: " + e.getMessage());
- }
- }
- }
-
- /**
- * Schedules the notification updater task if it hasn't been scheduled yet.
- */
- private void setupNotificationUpdaterIfNecessary() {
- if (notificationUpdater == null) {
- Log.d(TAG, "Setting up notification updater");
- notificationUpdater = new NotificationUpdater();
- notificationUpdaterFuture = notificationUpdateExecutor
- .scheduleAtFixedRate(notificationUpdater, 1, 1, TimeUnit.SECONDS);
- }
- }
-
- private void cancelNotificationUpdater() {
- boolean result = false;
- if (notificationUpdaterFuture != null) {
- result = notificationUpdaterFuture.cancel(true);
- }
- notificationUpdater = null;
- notificationUpdaterFuture = null;
- Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
- }
-
- private class NotificationUpdater implements Runnable {
- public void run() {
- Notification n = notificationManager.updateNotifications(downloads);
- if (n != null) {
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(R.id.notification_downloading, n);
- }
- }
- }
-
- private void postDownloaders() {
- new PostDownloaderTask(downloads).run();
-
- if (downloadPostFuture == null) {
- downloadPostFuture = notificationUpdateExecutor.scheduleAtFixedRate(
- new PostDownloaderTask(downloads), 1, 1, TimeUnit.SECONDS);
- }
- }
-
- private void shutdown() {
- // If the service was run for a very short time, the system may delay closing
- // the notification. Set the notification text now so that a misleading message
- // is not left on the notification.
- if (notificationUpdater != null) {
- notificationUpdater.run();
- }
- cancelNotificationUpdater();
- ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
- stopSelf();
- }
-}
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 976d8255f..87cbeda84 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
@@ -1,74 +1,72 @@
package de.danoeh.antennapod.core.service.download;
import android.content.Context;
-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 androidx.work.Constraints;
+import androidx.work.Data;
+import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.OutOfQuotaPolicy;
+import androidx.work.WorkManager;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import java.util.ArrayList;
-
-import static de.danoeh.antennapod.core.service.download.DownloadService.isDownloadingFile;
+import java.util.concurrent.TimeUnit;
public class DownloadServiceInterfaceImpl extends DownloadServiceInterface {
- private static final String TAG = "DownloadServiceInterface";
-
- public void download(Context context, boolean cleanupMedia, DownloadRequest... requests) {
- Intent intent = makeDownloadIntent(context, cleanupMedia, requests);
- if (intent != null) {
- ContextCompat.startForegroundService(context, intent);
+ public void downloadNow(Context context, FeedItem item, boolean ignoreConstraints) {
+ OneTimeWorkRequest.Builder workRequest = getRequest(context, item);
+ workRequest.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
+ if (ignoreConstraints) {
+ workRequest.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build());
+ } else {
+ workRequest.setConstraints(getConstraints());
}
+ WorkManager.getInstance(context).enqueueUniqueWork(item.getMedia().getDownload_url(),
+ ExistingWorkPolicy.KEEP, workRequest.build());
}
- public Intent makeDownloadIntent(Context context, boolean cleanupMedia, DownloadRequest... requests) {
- ArrayList<DownloadRequest> requestsToSend = new ArrayList<>();
- for (DownloadRequest request : requests) {
- if (!isDownloadingFile(request.getSource())) {
- requestsToSend.add(request);
- }
- }
- if (requestsToSend.isEmpty()) {
- return null;
- } else if (requestsToSend.size() > 100) {
- if (BuildConfig.DEBUG) {
- throw new IllegalArgumentException("Android silently drops intent payloads that are too large");
- } else {
- Log.d(TAG, "Too many download requests. Dropping some to avoid Android dropping all.");
- requestsToSend = new ArrayList<>(requestsToSend.subList(0, 100));
- }
- }
+ public void download(Context context, FeedItem item) {
+ OneTimeWorkRequest.Builder workRequest = getRequest(context, item);
+ workRequest.setConstraints(getConstraints());
+ WorkManager.getInstance(context).enqueueUniqueWork(item.getMedia().getDownload_url(),
+ ExistingWorkPolicy.KEEP, workRequest.build());
+ }
- Intent launchIntent = new Intent(context, DownloadService.class);
- launchIntent.putParcelableArrayListExtra(DownloadService.EXTRA_REQUESTS, requestsToSend);
- if (cleanupMedia) {
- launchIntent.putExtra(DownloadService.EXTRA_CLEANUP_MEDIA, true);
+ private static OneTimeWorkRequest.Builder getRequest(Context context, FeedItem item) {
+ OneTimeWorkRequest.Builder workRequest = new OneTimeWorkRequest.Builder(EpisodeDownloadWorker.class)
+ .setInitialDelay(0L, TimeUnit.MILLISECONDS)
+ .addTag(DownloadServiceInterface.WORK_TAG)
+ .addTag(DownloadServiceInterface.WORK_TAG_EPISODE_URL + item.getMedia().getDownload_url());
+ Data.Builder builder = new Data.Builder();
+ builder.putLong(WORK_DATA_MEDIA_ID, item.getMedia().getId());
+ if (!item.isTagged(FeedItem.TAG_QUEUE) && UserPreferences.enqueueDownloadedEpisodes()) {
+ DBWriter.addQueueItem(context, false, item.getId());
+ builder.putBoolean(WORK_DATA_WAS_QUEUED, true);
}
- return launchIntent;
+ workRequest.setInputData(builder.build());
+ return workRequest;
}
- public void refreshAllFeeds(Context context, boolean initiatedByUser) {
- FeedUpdateManager.runOnce(context);
+ private static Constraints getConstraints() {
+ Constraints.Builder constraints = new Constraints.Builder();
+ if (UserPreferences.isAllowMobileEpisodeDownload()) {
+ constraints.setRequiredNetworkType(NetworkType.CONNECTED);
+ } else {
+ constraints.setRequiredNetworkType(NetworkType.UNMETERED);
+ }
+ return constraints.build();
}
+ @Override
public void cancel(Context context, String url) {
- if (!DownloadService.isRunning) {
- return;
- }
- Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
- cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, url);
- cancelIntent.setPackage(context.getPackageName());
- context.sendBroadcast(cancelIntent);
+ WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG_EPISODE_URL + url);
}
+ @Override
public void cancelAll(Context context) {
- if (!DownloadService.isRunning) {
- return;
- }
- Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_ALL_DOWNLOADS);
- cancelIntent.setPackage(context.getPackageName());
- context.sendBroadcast(cancelIntent);
+ WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
deleted file mode 100644
index b9846c06c..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
+++ /dev/null
@@ -1,306 +0,0 @@
-package de.danoeh.antennapod.core.service.download;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.util.Log;
-import androidx.core.app.NotificationCompat;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.util.DownloadErrorLabel;
-import de.danoeh.antennapod.model.download.DownloadStatus;
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.core.util.gui.NotificationUtils;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
-import de.danoeh.antennapod.ui.appstartintent.DownloadAuthenticationActivityStarter;
-import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
-
-import java.util.List;
-
-public class DownloadServiceNotification {
- private static final String TAG = "DownloadSvcNotification";
-
- private final Context context;
- private NotificationCompat.Builder notificationCompatBuilder;
-
- public DownloadServiceNotification(Context context) {
- this.context = context;
- setupNotificationBuilders();
- }
-
- private void setupNotificationBuilders() {
- notificationCompatBuilder = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_DOWNLOADING)
- .setOngoing(false)
- .setWhen(0)
- .setOnlyAlertOnce(true)
- .setShowWhen(false)
- .setContentIntent(getNotificationContentIntent(context))
- .setSmallIcon(R.drawable.ic_notification_sync)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
- Log.d(TAG, "Notification set up");
- }
-
- /**
- * Updates the contents of the service's notifications. Should be called
- * after setupNotificationBuilders.
- */
- public Notification updateNotifications(List<Downloader> downloads) {
- if (notificationCompatBuilder == null) {
- return null;
- }
-
- String contentTitle;
- if (typeIsOnly(downloads, Feed.FEEDFILETYPE_FEED)) {
- contentTitle = context.getString(R.string.download_notification_title_feeds);
- } else if (typeIsOnly(downloads, FeedMedia.FEEDFILETYPE_FEEDMEDIA)) {
- contentTitle = context.getString(R.string.download_notification_title_episodes);
- } else {
- contentTitle = context.getString(R.string.download_notification_title);
- }
-
- int numDownloads = getNumberOfRunningDownloads(downloads);
- String contentText = context.getString(R.string.completing);
- String bigText = context.getString(R.string.completing);
- notificationCompatBuilder.clearActions();
- if (numDownloads > 0) {
- bigText = compileNotificationString(downloads);
- if (numDownloads == 1) {
- contentText = bigText;
- } else {
- contentText = context.getResources().getQuantityString(R.plurals.downloads_left,
- numDownloads, numDownloads);
- }
-
- Intent cancelDownloadsIntent = new Intent(DownloadService.ACTION_CANCEL_ALL_DOWNLOADS);
- cancelDownloadsIntent.setPackage(context.getPackageName());
- PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(context,
- R.id.pending_intent_download_cancel_all, cancelDownloadsIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- notificationCompatBuilder.addAction(new NotificationCompat.Action(
- R.drawable.ic_notification_cancel, context.getString(R.string.cancel_label), cancelPendingIntent));
- }
-
- notificationCompatBuilder.setContentTitle(contentTitle);
- notificationCompatBuilder.setContentText(contentText);
- notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
- return notificationCompatBuilder.build();
- }
-
- private int getNumberOfRunningDownloads(List<Downloader> downloads) {
- int running = 0;
- for (Downloader downloader : downloads) {
- if (!downloader.cancelled && !downloader.isFinished()) {
- running++;
- }
- }
- return running;
- }
-
- private boolean typeIsOnly(List<Downloader> downloads, int feedFileType) {
- for (Downloader downloader : downloads) {
- if (downloader.cancelled) {
- continue;
- }
- DownloadRequest request = downloader.getDownloadRequest();
- if (request.getFeedfileType() != feedFileType) {
- return false;
- }
- }
- return true;
- }
-
- private static String compileNotificationString(List<Downloader> downloads) {
- StringBuilder stringBuilder = new StringBuilder();
- for (int i = 0; i < downloads.size(); i++) {
- Downloader downloader = downloads.get(i);
- if (downloader.cancelled) {
- continue;
- }
- stringBuilder.append("• ");
- DownloadRequest request = downloader.getDownloadRequest();
- if (request.getTitle() != null) {
- stringBuilder.append(request.getTitle());
- } else {
- stringBuilder.append(request.getSource());
- }
- if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- stringBuilder.append(" (").append(request.getProgressPercent()).append("%)");
- } else if (request.getSource().startsWith(Feed.PREFIX_LOCAL_FOLDER)) {
- stringBuilder.append(" (").append(request.getSoFar())
- .append("/").append(request.getSize()).append(")");
- }
- if (i != downloads.size() - 1) {
- stringBuilder.append("\n");
- }
- }
- return stringBuilder.toString();
- }
-
- private static String createAutoDownloadNotificationContent(List<DownloadStatus> statuses) {
- int length = statuses.size();
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < length; i++) {
- sb.append("• ").append(statuses.get(i).getTitle());
- if (i != length - 1) {
- sb.append("\n");
- }
- }
-
- return sb.toString();
- }
-
- private String createFailedDownloadNotificationContent(List<DownloadStatus> statuses) {
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < statuses.size(); i++) {
- if (statuses.get(i) == null || statuses.get(i).isSuccessful()) {
- continue;
- }
- sb.append("• ").append(statuses.get(i).getTitle());
- if (statuses.get(i).getReason() != null) {
- sb.append(": ").append(context.getString(DownloadErrorLabel.from(statuses.get(i).getReason())));
- }
- if (i != statuses.size() - 1) {
- sb.append("\n");
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Creates a notification at the end of the service lifecycle to notify the
- * user about the number of completed downloads. A report will only be
- * created if there is at least one failed download excluding images
- */
- public void updateReport(List<DownloadStatus> reportQueue, boolean showAutoDownloadReport,
- List<DownloadRequest> failedRequests) {
- // check if report should be created
- boolean createReport = false;
- int failedDownloads = 0;
-
- // a download report is created if at least one download has failed
- // (excluding failed image downloads)
- for (DownloadStatus status : reportQueue) {
- if (status == null || status.isCancelled()) {
- continue;
- }
- if (status.isSuccessful()) {
- createReport |= showAutoDownloadReport && !status.isInitiatedByUser()
- && status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA;
- } else {
- failedDownloads++;
- createReport = true;
- }
- }
-
- if (!createReport) {
- Log.d(TAG, "No report is created");
- return;
- }
- Log.d(TAG, "Creating report");
- if (failedDownloads == 0) {
- createAutoDownloadReportNotification(reportQueue);
- } else {
- createDownloadFailedNotification(reportQueue, failedRequests);
- }
- Log.d(TAG, "Download report notification was posted");
- }
-
- private void createAutoDownloadReportNotification(List<DownloadStatus> reportQueue) {
- PendingIntent intent = getAutoDownloadReportNotificationContentIntent(context);
- String content = createAutoDownloadNotificationContent(reportQueue);
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context,
- NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD);
- builder.setTicker(context.getString(R.string.auto_download_report_title))
- .setContentTitle(context.getString(R.string.auto_download_report_title))
- .setContentText(content)
- .setStyle(new NotificationCompat.BigTextStyle().bigText(content))
- .setSmallIcon(R.drawable.ic_notification_new)
- .setContentIntent(intent)
- .setAutoCancel(true)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
- NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(R.id.notification_auto_download_report, builder.build());
- }
-
- private void createDownloadFailedNotification(List<DownloadStatus> reportQueue,
- List<DownloadRequest> failedRequests) {
- Intent retryIntent = DownloadServiceInterface.get().makeDownloadIntent(context,
- false, failedRequests.toArray(new DownloadRequest[0]));
- PendingIntent retryPendingIntent = null;
- if (retryIntent != null && Build.VERSION.SDK_INT >= 26) {
- retryPendingIntent = PendingIntent.getForegroundService(context, R.id.pending_intent_download_service_retry,
- retryIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- } else if (retryIntent != null) {
- retryPendingIntent = PendingIntent.getService(context,
- R.id.pending_intent_download_service_retry, retryIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
- | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- }
- PendingIntent intent = getReportNotificationContentIntent(context);
- String content = createFailedDownloadNotificationContent(reportQueue);
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context,
- NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR);
- builder.setTicker(context.getString(R.string.download_report_title))
- .setContentTitle(context.getString(R.string.download_report_title))
- .setContentText(content)
- .setStyle(new NotificationCompat.BigTextStyle().bigText(content))
- .setSmallIcon(R.drawable.ic_notification_sync_error)
- .setContentIntent(intent)
- .setAutoCancel(true);
- if (retryPendingIntent != null) {
- builder.addAction(new NotificationCompat.Action(
- R.drawable.ic_notification_sync, context.getString(R.string.retry_label), retryPendingIntent));
- }
- builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
- NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(R.id.notification_download_report, builder.build());
- }
-
- public void postAuthenticationNotification(final DownloadRequest downloadRequest) {
- final String resourceTitle = (downloadRequest.getTitle() != null) ?
- downloadRequest.getTitle() : downloadRequest.getSource();
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_USER_ACTION);
- builder.setTicker(context.getText(R.string.authentication_notification_title))
- .setContentTitle(context.getText(R.string.authentication_notification_title))
- .setContentText(context.getText(R.string.authentication_notification_msg))
- .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getText(R.string.authentication_notification_msg)
- + ": " + resourceTitle))
- .setSmallIcon(R.drawable.ic_notification_key)
- .setAutoCancel(true)
- .setContentIntent(new DownloadAuthenticationActivityStarter(
- context, downloadRequest.getFeedfileId(), downloadRequest).getPendingIntent());
- builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
- NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(downloadRequest.getSource().hashCode(), builder.build());
- }
-
- public PendingIntent getReportNotificationContentIntent(Context context) {
- Intent intent = new MainActivityStarter(context)
- .withFragmentLoaded("DownloadsFragment")
- .withFragmentArgs("show_logs", true)
- .getIntent();
- return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- }
-
- public PendingIntent getAutoDownloadReportNotificationContentIntent(Context context) {
- Intent intent = new MainActivityStarter(context).withFragmentLoaded("QueueFragment").getIntent();
- return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- }
-
- public PendingIntent getNotificationContentIntent(Context context) {
- Intent intent = new MainActivityStarter(context).withFragmentLoaded("DownloadsFragment").getIntent();
- return PendingIntent.getActivity(context,
- R.id.pending_intent_download_service_notification, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
index f7f5e8e9c..35247509d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
@@ -9,7 +9,7 @@ import java.util.concurrent.Callable;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
/**
@@ -25,15 +25,15 @@ public abstract class Downloader implements Callable<Downloader> {
@NonNull
final DownloadRequest request;
@NonNull
- final DownloadStatus result;
+ final DownloadResult result;
Downloader(@NonNull DownloadRequest request) {
super();
this.request = request;
this.request.setStatusMsg(R.string.download_pending);
this.cancelled = false;
- this.result = new DownloadStatus(0, request.getTitle(), request.getFeedfileId(), request.getFeedfileType(),
- false, cancelled, false, null, new Date(), null, request.isInitiatedByUser());
+ this.result = new DownloadResult(0, request.getTitle(), request.getFeedfileId(), request.getFeedfileType(),
+ false, null, new Date(), null);
}
protected abstract void download();
@@ -63,7 +63,7 @@ public abstract class Downloader implements Callable<Downloader> {
}
@NonNull
- public DownloadStatus getResult() {
+ public DownloadResult getResult() {
return result;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java
new file mode 100644
index 000000000..c428bc861
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java
@@ -0,0 +1,265 @@
+package de.danoeh.antennapod.core.service.download;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.work.Data;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+import de.danoeh.antennapod.core.ClientConfigurator;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
+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.FeedMedia;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
+import org.apache.commons.io.FileUtils;
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+public class EpisodeDownloadWorker extends Worker {
+ private static final String TAG = "EpisodeDownloadWorker";
+ private static final Map<String, Integer> notificationProgress = new HashMap<>();
+
+ private Downloader downloader = null;
+
+ public EpisodeDownloadWorker(@NonNull Context context, @NonNull WorkerParameters params) {
+ super(context, params);
+ }
+
+ @Override
+ @NonNull
+ public Result doWork() {
+ ClientConfigurator.initialize(getApplicationContext());
+ long mediaId = getInputData().getLong(DownloadServiceInterface.WORK_DATA_MEDIA_ID, 0);
+ FeedMedia media = DBReader.getFeedMedia(mediaId);
+ if (media == null) {
+ return Result.failure();
+ }
+
+ DownloadRequest request = DownloadRequestCreator.create(media).build();
+ Thread progressUpdaterThread = new Thread() {
+ @Override
+ public void run() {
+ while (!isInterrupted()) {
+ try {
+ Thread.sleep(1000);
+ notificationProgress.put(media.getEpisodeTitle(), request.getProgressPercent());
+ setProgressAsync(
+ new Data.Builder()
+ .putInt(DownloadServiceInterface.WORK_DATA_PROGRESS, request.getProgressPercent())
+ .build())
+ .get();
+ sendProgressNotification();
+ } catch (InterruptedException | ExecutionException e) {
+ return;
+ }
+ }
+ }
+ };
+ progressUpdaterThread.start();
+ final Result result = performDownload(media, request);
+ progressUpdaterThread.interrupt();
+ try {
+ progressUpdaterThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ notificationProgress.remove(media.getEpisodeTitle());
+ if (notificationProgress.isEmpty()) {
+ NotificationManager nm = (NotificationManager) getApplicationContext()
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(R.id.notification_downloading);
+ }
+ Log.d(TAG, "Worker for " + media.getDownload_url() + " returned.");
+ return result;
+ }
+
+ @Override
+ public void onStopped() {
+ super.onStopped();
+ if (downloader != null) {
+ downloader.cancel();
+ }
+ }
+
+ private Result performDownload(FeedMedia media, DownloadRequest request) {
+ File dest = new File(request.getDestination());
+ if (!dest.exists()) {
+ try {
+ dest.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to create file");
+ }
+ }
+
+ if (dest.exists()) {
+ media.setFile_url(request.getDestination());
+ try {
+ DBWriter.setFeedMedia(media).get();
+ } catch (Exception e) {
+ Log.e(TAG, "ExecutionException in writeFileUrl: " + e.getMessage());
+ }
+ }
+
+ downloader = new DefaultDownloaderFactory().create(request);
+ if (downloader == null) {
+ Log.d(TAG, "Unable to create downloader");
+ return Result.failure();
+ }
+
+ try {
+ downloader.call();
+ } catch (Exception e) {
+ DBWriter.addDownloadStatus(downloader.getResult());
+ if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent.class)) {
+ sendMessage(request.getTitle(), false);
+ } else {
+ sendErrorNotification();
+ }
+ return Result.failure();
+ }
+
+ if (downloader.cancelled) {
+ if (getInputData().getBoolean(DownloadServiceInterface.WORK_DATA_WAS_QUEUED, false)) {
+ try {
+ DBWriter.removeQueueItem(getApplicationContext(), false, media.getItem()).get();
+ } catch (ExecutionException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return Result.success();
+ }
+
+ DownloadResult status = downloader.getResult();
+ if (status.isSuccessful()) {
+ MediaDownloadedHandler handler = new MediaDownloadedHandler(
+ getApplicationContext(), downloader.getResult(), request);
+ handler.run();
+ DBWriter.addDownloadStatus(handler.getUpdatedStatus());
+ return Result.success();
+ }
+
+ if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
+ && Integer.parseInt(status.getReasonDetailed()) == 416) {
+ Log.d(TAG, "Requested invalid range, restarting download from the beginning");
+ FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination()));
+ sendMessage(request.getTitle(), true);
+ return retry3times();
+ }
+
+ Log.e(TAG, "Download failed");
+ DBWriter.addDownloadStatus(status);
+ if (status.getReason() == DownloadError.ERROR_FORBIDDEN
+ || status.getReason() == DownloadError.ERROR_NOT_FOUND
+ || status.getReason() == DownloadError.ERROR_UNAUTHORIZED
+ || status.getReason() == DownloadError.ERROR_IO_BLOCKED) {
+ // Fail fast, these are probably unrecoverable
+ if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent.class)) {
+ sendMessage(request.getTitle(), false);
+ } else {
+ sendErrorNotification();
+ }
+ return Result.failure();
+ }
+ sendMessage(request.getTitle(), true);
+ return retry3times();
+ }
+
+ private Result retry3times() {
+ if (getRunAttemptCount() < 2) {
+ return Result.retry();
+ } else {
+ sendErrorNotification();
+ return Result.failure();
+ }
+ }
+
+ private void sendMessage(String episodeTitle, boolean retrying) {
+ if (episodeTitle.length() > 20) {
+ episodeTitle = episodeTitle.substring(0, 19) + "…";
+ }
+ EventBus.getDefault().post(new MessageEvent(
+ getApplicationContext().getString(
+ retrying ? R.string.download_error_retrying : R.string.download_error_not_retrying,
+ episodeTitle), (ctx) -> new MainActivityStarter(ctx).withDownloadLogsOpen().start(),
+ getApplicationContext().getString(R.string.download_error_details)));
+ }
+
+ private PendingIntent getDownloadLogsIntent(Context context) {
+ Intent intent = new MainActivityStarter(context).withDownloadLogsOpen().getIntent();
+ return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
+ }
+
+ private PendingIntent getDownloadsIntent(Context context) {
+ Intent intent = new MainActivityStarter(context).withFragmentLoaded("DownloadsFragment").getIntent();
+ return PendingIntent.getActivity(context, R.id.pending_intent_download_service_notification, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
+ }
+
+ private void sendErrorNotification() {
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),
+ NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR);
+ builder.setTicker(getApplicationContext().getString(R.string.download_report_title))
+ .setContentTitle(getApplicationContext().getString(R.string.download_report_title))
+ .setContentText(getApplicationContext().getString(R.string.download_error_tap_for_details))
+ .setSmallIcon(R.drawable.ic_notification_sync_error)
+ .setContentIntent(getDownloadLogsIntent(getApplicationContext()))
+ .setAutoCancel(true);
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ NotificationManager nm = (NotificationManager) getApplicationContext()
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(R.id.notification_download_report, builder.build());
+ }
+
+ private void sendProgressNotification() {
+ StringBuilder bigTextB = new StringBuilder();
+ Map<String, Integer> progressCopy = new HashMap<>(notificationProgress);
+ for (Map.Entry<String, Integer> entry : progressCopy.entrySet()) {
+ bigTextB.append(String.format(Locale.getDefault(), "%s (%d%%)\n", entry.getKey(), entry.getValue()));
+ }
+ String bigText = bigTextB.toString().trim();
+ String contentText;
+ if (notificationProgress.size() == 1) {
+ contentText = bigText;
+ } else {
+ contentText = getApplicationContext().getResources().getQuantityString(R.plurals.downloads_left,
+ notificationProgress.size(), notificationProgress.size());
+ }
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),
+ NotificationUtils.CHANNEL_ID_DOWNLOADING);
+ builder.setTicker(getApplicationContext().getString(R.string.download_notification_title_episodes))
+ .setContentTitle(getApplicationContext().getString(R.string.download_notification_title_episodes))
+ .setContentText(contentText)
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
+ .setContentIntent(getDownloadsIntent(getApplicationContext()))
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setWhen(0)
+ .setOnlyAlertOnce(true)
+ .setShowWhen(false)
+ .setSmallIcon(R.drawable.ic_notification_sync)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ NotificationManager nm = (NotificationManager) getApplicationContext()
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(R.id.notification_downloading, builder.build());
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
index a0a0615cb..949f9966b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -5,7 +5,7 @@ import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import okhttp3.CacheControl;
import okhttp3.internal.http.StatusLine;
@@ -149,12 +149,12 @@ public class HttpDownloader extends Downloader {
request.setSize(responseBody.contentLength() + request.getSoFar());
Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
- request.setSize(DownloadStatus.SIZE_UNKNOWN);
+ request.setSize(DownloadResult.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
Log.d(TAG, "Free space is " + freeSpace);
- if (request.getSize() != DownloadStatus.SIZE_UNKNOWN && request.getSize() > freeSpace) {
+ if (request.getSize() != DownloadResult.SIZE_UNKNOWN && request.getSize() > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
return;
}
@@ -175,7 +175,7 @@ public class HttpDownloader extends Downloader {
} else {
// check if size specified in the response header is the same as the size of the
// written file. This check cannot be made if compression was used
- if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN
+ if (!isGzip && request.getSize() != DownloadResult.SIZE_UNKNOWN
&& request.getSoFar() != request.getSize()) {
onFail(DownloadError.ERROR_IO_WRONG_SIZE, "Download completed but size: "
+ request.getSoFar() + " does not equal expected size " + request.getSize());
@@ -267,7 +267,8 @@ public class HttpDownloader extends Downloader {
} else if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
error = DownloadError.ERROR_FORBIDDEN;
details = String.valueOf(response.code());
- } else if (response.code() == HttpURLConnection.HTTP_NOT_FOUND) {
+ } else if (response.code() == HttpURLConnection.HTTP_NOT_FOUND
+ || response.code() == HttpURLConnection.HTTP_GONE) {
error = DownloadError.ERROR_NOT_FOUND;
details = String.valueOf(response.code());
} else {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/LocalFeedStubDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/LocalFeedStubDownloader.java
deleted file mode 100644
index 750255958..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/LocalFeedStubDownloader.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.danoeh.antennapod.core.service.download;
-
-import androidx.annotation.NonNull;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-
-/**
- * This does not actually download, but it keeps track of a local feed's refresh state.
- */
-public class LocalFeedStubDownloader extends Downloader {
-
- public LocalFeedStubDownloader(@NonNull DownloadRequest request) {
- super(request);
- }
-
- @Override
- protected void download() {
- }
-} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
deleted file mode 100644
index 937f051ec..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.danoeh.antennapod.core.service.download.handler;
-
-import android.util.Log;
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.core.storage.DBWriter;
-
-/**
- * Handles failed downloads.
- * <p/>
- * If the file has been partially downloaded, this handler will set the file_url of the FeedFile to the location
- * of the downloaded file.
- * <p/>
- * Currently, this handler only handles FeedMedia objects, because Feeds and FeedImages are deleted if the download fails.
- */
-public class FailedDownloadHandler implements Runnable {
- private static final String TAG = "FailedDownloadHandler";
- private final DownloadRequest request;
-
- public FailedDownloadHandler(DownloadRequest request) {
- this.request = request;
- }
-
- @Override
- public void run() {
- if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
- DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
- } else if (request.isDeleteOnFailure()) {
- Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
index 1118b93cd..5da250e15 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
@@ -8,7 +8,7 @@ import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.parser.feed.FeedHandler;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
@@ -25,15 +25,15 @@ import java.util.concurrent.Callable;
public class FeedParserTask implements Callable<FeedHandlerResult> {
private static final String TAG = "FeedParserTask";
private final DownloadRequest request;
- private DownloadStatus downloadStatus;
+ private DownloadResult downloadResult;
private boolean successful = true;
public FeedParserTask(DownloadRequest request) {
this.request = request;
- downloadStatus = new DownloadStatus(
+ downloadResult = new DownloadResult(
0, request.getTitle(), 0, request.getFeedfileType(), false,
- false, true, DownloadError.ERROR_REQUEST_ERROR, new Date(),
- "Unknown error: Status not set", request.isInitiatedByUser());
+ DownloadError.ERROR_REQUEST_ERROR, new Date(),
+ "Unknown error: Status not set");
}
@Override
@@ -87,12 +87,12 @@ public class FeedParserTask implements Callable<FeedHandlerResult> {
}
if (successful) {
- downloadStatus = new DownloadStatus(feed, feed.getHumanReadableIdentifier(), DownloadError.SUCCESS,
- successful, reasonDetailed, request.isInitiatedByUser());
+ downloadResult = new DownloadResult(feed, feed.getHumanReadableIdentifier(), DownloadError.SUCCESS,
+ successful, reasonDetailed);
return result;
} else {
- downloadStatus = new DownloadStatus(feed, feed.getHumanReadableIdentifier(), reason,
- successful, reasonDetailed, request.isInitiatedByUser());
+ downloadResult = new DownloadResult(feed, feed.getHumanReadableIdentifier(), reason,
+ successful, reasonDetailed);
return null;
}
}
@@ -120,7 +120,7 @@ public class FeedParserTask implements Callable<FeedHandlerResult> {
}
@NonNull
- public DownloadStatus getDownloadStatus() {
- return downloadStatus;
+ public DownloadResult getDownloadStatus() {
+ return downloadResult;
}
}
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 9cb1166b4..3b72ed164 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
@@ -3,7 +3,7 @@ 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.download.DownloadResult;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
@@ -30,7 +30,7 @@ public class FeedSyncTask {
}
@NonNull
- public DownloadStatus getDownloadStatus() {
+ public DownloadResult getDownloadStatus() {
return task.getDownloadStatus();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
index c632bf1e0..a46b4c6d0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
@@ -13,7 +13,7 @@ import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
@@ -30,9 +30,9 @@ public class MediaDownloadedHandler implements Runnable {
private static final String TAG = "MediaDownloadedHandler";
private final DownloadRequest request;
private final Context context;
- private DownloadStatus updatedStatus;
+ private DownloadResult updatedStatus;
- public MediaDownloadedHandler(@NonNull Context context, @NonNull DownloadStatus status,
+ public MediaDownloadedHandler(@NonNull Context context, @NonNull DownloadResult status,
@NonNull DownloadRequest request) {
this.request = request;
this.context = context;
@@ -94,8 +94,8 @@ public class MediaDownloadedHandler implements Runnable {
Log.e(TAG, "MediaHandlerThread was interrupted");
} catch (ExecutionException e) {
Log.e(TAG, "ExecutionException in MediaHandlerThread: " + e.getMessage());
- updatedStatus = new DownloadStatus(media, media.getEpisodeTitle(),
- DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage(), request.isInitiatedByUser());
+ updatedStatus = new DownloadResult(media, media.getEpisodeTitle(),
+ DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
}
if (item != null) {
@@ -107,7 +107,7 @@ public class MediaDownloadedHandler implements Runnable {
}
@NonNull
- public DownloadStatus getUpdatedStatus() {
+ public DownloadResult getUpdatedStatus() {
return updatedStatus;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/PostDownloaderTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/PostDownloaderTask.java
deleted file mode 100644
index 5d2c48679..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/PostDownloaderTask.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.danoeh.antennapod.core.service.download.handler;
-
-import de.danoeh.antennapod.core.event.DownloadEvent;
-import de.danoeh.antennapod.core.service.download.Downloader;
-import org.greenrobot.eventbus.EventBus;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class PostDownloaderTask implements Runnable {
- private List<Downloader> downloads;
-
- public PostDownloaderTask(List<Downloader> downloads) {
- this.downloads = downloads;
- }
-
- @Override
- public void run() {
- List<Downloader> runningDownloads = new ArrayList<>();
- for (Downloader downloader : downloads) {
- if (!downloader.cancelled) {
- runningDownloads.add(downloader);
- }
- }
- List<Downloader> list = Collections.unmodifiableList(runningDownloads);
- EventBus.getDefault().postSticky(DownloadEvent.refresh(list));
- }
-}
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 42631296b..6fc9035ca 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
@@ -824,7 +824,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
&& SleepTimerPreferences.autoEnable() && autoEnableByTime && !sleepTimerActive()) {
setSleepTimer(SleepTimerPreferences.timerMillis());
EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_enabled_label),
- PlaybackService.this::disableSleepTimer));
+ (ctx) -> disableSleepTimer(), getString(R.string.undo)));
}
loadQueueForMediaSession();
break;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
index 0f3121551..dbbfba379 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
@@ -9,12 +9,10 @@ import java.util.List;
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.DownloadRequestCreator;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.util.PlaybackStatus;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedPreferences;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.PowerUtils;
@@ -97,13 +95,9 @@ public class AutomaticDownloadAlgorithm {
if (itemsToDownload.size() > 0) {
Log.d(TAG, "Enqueueing " + itemsToDownload.size() + " items for download");
- List<DownloadRequest> requests = new ArrayList<>();
for (FeedItem episode : itemsToDownload) {
- DownloadRequest.Builder request = DownloadRequestCreator.create(episode.getMedia());
- request.withInitiatedByUser(false);
- requests.add(request.build());
+ DownloadServiceInterface.get().download(context, episode);
}
- DownloadServiceInterface.get().download(context, false, requests.toArray(new DownloadRequest[0]));
}
}
};
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index 94a7334f3..d83557b0c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -16,10 +16,9 @@ import java.util.List;
import java.util.Map;
import de.danoeh.antennapod.core.util.LongList;
-import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
+import de.danoeh.antennapod.core.util.comparator.DownloadResultComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator;
-import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
@@ -28,14 +27,15 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.model.feed.SubscriptionsFilter;
+import de.danoeh.antennapod.storage.preferences.UserPreferences;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import de.danoeh.antennapod.storage.database.mapper.ChapterCursorMapper;
-import de.danoeh.antennapod.storage.database.mapper.DownloadStatusCursorMapper;
+import de.danoeh.antennapod.storage.database.mapper.DownloadResultCursorMapper;
import de.danoeh.antennapod.storage.database.mapper.FeedCursorMapper;
import de.danoeh.antennapod.storage.database.mapper.FeedItemCursorMapper;
import de.danoeh.antennapod.storage.database.mapper.FeedMediaCursorMapper;
import de.danoeh.antennapod.storage.database.mapper.FeedPreferencesCursorMapper;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
/**
* Provides methods for reading data from the AntennaPod database.
@@ -394,17 +394,17 @@ public final class DBReader {
* @return A list with DownloadStatus objects that represent the download log.
* The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
*/
- public static List<DownloadStatus> getDownloadLog() {
+ public static List<DownloadResult> getDownloadLog() {
Log.d(TAG, "getDownloadLog() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try (Cursor cursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE)) {
- List<DownloadStatus> downloadLog = new ArrayList<>(cursor.getCount());
+ List<DownloadResult> downloadLog = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
- downloadLog.add(DownloadStatusCursorMapper.convert(cursor));
+ downloadLog.add(DownloadResultCursorMapper.convert(cursor));
}
- Collections.sort(downloadLog, new DownloadStatusComparator());
+ Collections.sort(downloadLog, new DownloadResultComparator());
return downloadLog;
} finally {
adapter.close();
@@ -418,17 +418,17 @@ public final class DBReader {
* @return A list with DownloadStatus objects that represent the feed's download log,
* newest events first.
*/
- public static List<DownloadStatus> getFeedDownloadLog(long feedId) {
+ public static List<DownloadResult> getFeedDownloadLog(long feedId) {
Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feedId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try (Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feedId)) {
- List<DownloadStatus> downloadLog = new ArrayList<>(cursor.getCount());
+ List<DownloadResult> downloadLog = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
- downloadLog.add(DownloadStatusCursorMapper.convert(cursor));
+ downloadLog.add(DownloadResultCursorMapper.convert(cursor));
}
- Collections.sort(downloadLog, new DownloadStatusComparator());
+ Collections.sort(downloadLog, new DownloadResultComparator());
return downloadLog;
} finally {
adapter.close();
@@ -717,10 +717,10 @@ public final class DBReader {
}
}
- public static List<FeedItem> getFeedItemsWithMedia(Long[] mediaIds) {
+ public static List<FeedItem> getFeedItemsWithUrl(List<String> urls) {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- try (Cursor itemCursor = adapter.getFeedItemCursorByMediaIds(mediaIds)) {
+ try (Cursor itemCursor = adapter.getFeedItemCursorByUrl(urls)) {
List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
loadAdditionalFeedItemListData(items);
Collections.sort(items, new PlaybackCompletionDateComparator());
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 8b79d594c..e3ac7a7e1 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
@@ -7,14 +7,13 @@ 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.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.download.DownloadResult;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
@@ -114,21 +113,6 @@ public final class DBTasks {
EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found)));
}
- public static List<FeedItem> enqueueFeedItemsToDownload(final Context context,
- List<FeedItem> items) throws InterruptedException, ExecutionException {
- List<FeedItem> itemsToEnqueue = new ArrayList<>();
- if (UserPreferences.enqueueDownloadedEpisodes()) {
- LongList queueIDList = DBReader.getQueueIDList();
- for (FeedItem item : items) {
- if (!queueIDList.contains(item.getId())) {
- itemsToEnqueue.add(item);
- }
- }
- DBWriter.addQueueItem(context, false, itemsToEnqueue.toArray(new FeedItem[0])).get();
- }
- return itemsToEnqueue;
- }
-
/**
* Looks for non-downloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
@@ -267,13 +251,13 @@ public final class DBTasks {
FeedItem possibleDuplicate = searchFeedItemGuessDuplicate(newFeed.getItems(), item);
if (!newFeed.isLocalFeed() && possibleDuplicate != null && item != possibleDuplicate) {
// Canonical episode is the first one returned (usually oldest)
- DBWriter.addDownloadStatus(new DownloadStatus(savedFeed,
+ DBWriter.addDownloadStatus(new DownloadResult(savedFeed,
item.getTitle(), DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false,
"The podcast host appears to have added the same episode twice. "
+ "AntennaPod still refreshed the feed and attempted to repair it."
+ "\n\nOriginal episode:\n" + duplicateEpisodeDetails(item)
+ "\n\nSecond episode that is also in the feed:\n"
- + duplicateEpisodeDetails(possibleDuplicate), false));
+ + duplicateEpisodeDetails(possibleDuplicate)));
continue;
}
@@ -282,13 +266,13 @@ public final class DBTasks {
oldItem = searchFeedItemGuessDuplicate(savedFeed.getItems(), item);
if (oldItem != null) {
Log.d(TAG, "Repaired duplicate: " + oldItem + ", " + item);
- DBWriter.addDownloadStatus(new DownloadStatus(savedFeed,
+ DBWriter.addDownloadStatus(new DownloadResult(savedFeed,
item.getTitle(), DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false,
"The podcast host changed the ID of an existing episode instead of just "
+ "updating the episode itself. AntennaPod still refreshed the feed and "
+ "attempted to repair it."
+ "\n\nOriginal episode:\n" + duplicateEpisodeDetails(oldItem)
- + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item), false));
+ + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item)));
oldItem.setItemIdentifier(item.getItemIdentifier());
if (oldItem.isPlayed() && oldItem.getMedia() != null) {
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 dcee8a45a..4815737f4 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
@@ -36,7 +36,7 @@ import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.model.download.DownloadStatus;
+import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.IntentUtils;
@@ -299,7 +299,7 @@ public class DBWriter {
*
* @param status The DownloadStatus object.
*/
- public static Future<?> addDownloadStatus(final DownloadStatus status) {
+ public static Future<?> addDownloadStatus(final DownloadResult status) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
index b81f281e8..4ed17c43f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
@@ -8,9 +8,9 @@ import androidx.annotation.Nullable;
import java.util.List;
import java.util.Random;
-import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
import de.danoeh.antennapod.model.playback.Playable;
@@ -74,7 +74,7 @@ class ItemEnqueuePositionCalculator {
}
return curItem != null
&& curItem.getMedia() != null
- && DownloadService.isDownloadingFile(curItem.getMedia().getDownload_url());
+ && DownloadServiceInterface.get().isDownloadingEpisode(curItem.getMedia().getDownload_url());
}
private static int getCurrentlyPlayingPosition(@NonNull List<FeedItem> curQueue,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
index 50b4d411f..d848b5a30 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
@@ -24,10 +24,10 @@ public class FeedItemUtil {
return -1;
}
- public static int indexOfItemWithMediaId(List<FeedItem> items, long mediaId) {
- for(int i=0; i < items.size(); i++) {
+ public static int indexOfItemWithDownloadUrl(List<FeedItem> items, String downloadUrl) {
+ for (int i = 0; i < items.size(); i++) {
FeedItem item = items.get(i);
- if(item != null && item.getMedia() != null && item.getMedia().getId() == mediaId) {
+ if (item != null && item.getMedia() != null && item.getMedia().getDownload_url().equals(downloadUrl)) {
return i;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadResultComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadResultComparator.java
new file mode 100644
index 000000000..d1d50fc8a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadResultComparator.java
@@ -0,0 +1,14 @@
+package de.danoeh.antennapod.core.util.comparator;
+
+import java.util.Comparator;
+
+import de.danoeh.antennapod.model.download.DownloadResult;
+
+/** Compares the completion date of two DownloadResult objects. */
+public class DownloadResultComparator implements Comparator<DownloadResult> {
+
+ @Override
+ public int compare(DownloadResult lhs, DownloadResult rhs) {
+ return rhs.getCompletionDate().compareTo(lhs.getCompletionDate());
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java
deleted file mode 100644
index 68b38ec7f..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package de.danoeh.antennapod.core.util.comparator;
-
-import java.util.Comparator;
-
-import de.danoeh.antennapod.model.download.DownloadStatus;
-
-/** Compares the completion date of two Downloadstatus objects. */
-public class DownloadStatusComparator implements Comparator<DownloadStatus> {
-
- @Override
- public int compare(DownloadStatus lhs, DownloadStatus rhs) {
- return rhs.getCompletionDate().compareTo(lhs.getCompletionDate());
- }
-
-}
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 d66bd2360..6a7e51bac 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
@@ -13,7 +13,6 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -26,7 +25,6 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import static de.danoeh.antennapod.core.util.FeedItemUtil.getIdList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -255,63 +253,6 @@ public class DbTasksTest {
}
}
- @Test
- public void testAddQueueItemsInDownload_EnqueueEnabled() throws Exception {
- // Setup test data / environment
- UserPreferences.setEnqueueDownloadedEpisodes(true);
- UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);
-
- List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
- List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
-
- DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
- // the first item fis1.get(0) is already in the queue
- FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
-
- // Expectations:
- List<FeedItem> expectedEnqueued = Arrays.asList(fis1.get(1), fis2.get(2), fis2.get(1));
- List<FeedItem> expectedQueue = new ArrayList<>();
- expectedQueue.addAll(DBReader.getQueue());
- expectedQueue.addAll(expectedEnqueued);
-
- // Run actual test and assert results
- List<? extends FeedItem> actualEnqueued =
- DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
-
- assertEqualsByIds("Only items not in the queue are enqueued", expectedEnqueued, actualEnqueued);
- assertEqualsByIds("Queue has new items appended", expectedQueue, DBReader.getQueue());
- }
-
- @Test
- public void testAddQueueItemsInDownload_EnqueueDisabled() throws Exception {
- // Setup test data / environment
- UserPreferences.setEnqueueDownloadedEpisodes(false);
-
- List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
- List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
-
- DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
- FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
-
- // Expectations:
- List<FeedItem> expectedEnqueued = Collections.emptyList();
- List<FeedItem> expectedQueue = DBReader.getQueue();
-
- // Run actual test and assert results
- List<? extends FeedItem> actualEnqueued =
- DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
-
- assertEqualsByIds("No item is enqueued", expectedEnqueued, actualEnqueued);
- assertEqualsByIds("Queue is unchanged", expectedQueue, DBReader.getQueue());
- }
-
- private void assertEqualsByIds(String msg, List<? extends FeedItem> expected, List<? extends FeedItem> actual) {
- // assert only the IDs, so that any differences are easily to spot.
- List<Long> expectedIds = getIdList(expected);
- List<Long> actualIds = getIdList(actual);
- assertEquals(msg, expectedIds, actualIds);
- }
-
private Feed createSavedFeed(String title, int numFeedItems) {
final Feed feed = new Feed("url", null, title);
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
index 376e0e65c..2594fabf6 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
@@ -1,7 +1,8 @@
package de.danoeh.antennapod.core.storage;
-import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.model.playback.RemoteMedia;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
+import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterfaceStub;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -21,8 +22,6 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedMother;
import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
import de.danoeh.antennapod.model.playback.Playable;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
import static de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation.AFTER_CURRENTLY_PLAYING;
import static de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation.BACK;
@@ -74,6 +73,7 @@ public class ItemEnqueuePositionCalculatorTest {
*/
@Test
public void test() {
+ DownloadServiceInterface.setImpl(new DownloadServiceInterfaceStub());
ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
// shallow copy to which the test will add items
@@ -128,95 +128,6 @@ public class ItemEnqueuePositionCalculatorTest {
}
- @RunWith(Parameterized.class)
- public static class PreserveDownloadOrderTest {
- /**
- * The test covers the use case that when user initiates multiple downloads in succession,
- * resulting in multiple addQueueItem() calls in succession.
- * the items in the queue will be in the same order as the order user taps to download
- */
- @Parameters(name = "{index}: case<{0}>")
- public static Iterable<Object[]> data() {
- // Attempts to make test more readable by showing the expected list of ids
- // (rather than the expected positions)
- return Arrays.asList(new Object[][] {
- {"download order test, enqueue default",
- concat(QUEUE_DEFAULT_IDS, 101L),
- concat(QUEUE_DEFAULT_IDS, list(101L, 102L)),
- concat(QUEUE_DEFAULT_IDS, list(101L, 102L, 103L)),
- BACK, QUEUE_DEFAULT, ID_CURRENTLY_PLAYING_NULL},
- {"download order test, enqueue at front (currently playing has no effect)",
- concat(101L, QUEUE_DEFAULT_IDS),
- concat(list(101L, 102L), QUEUE_DEFAULT_IDS),
- concat(list(101L, 103L, 102L), QUEUE_DEFAULT_IDS),
- // ^ 103 is put ahead of 102, after 102 failed.
- // It is a limitation as the logic can't tell 102 download has failed
- // (as opposed to simply being enqueued)
- FRONT, QUEUE_DEFAULT, 11L}, // 11 is at the front, currently playing
- {"download order test, enqueue after currently playing",
- list(11L, 101L, 12L, 13L, 14L),
- list(11L, 101L, 102L, 12L, 13L, 14L),
- list(11L, 101L, 103L, 102L, 12L, 13L, 14L),
- AFTER_CURRENTLY_PLAYING, QUEUE_DEFAULT, 11L} // 11 is at the front, currently playing
- });
- }
-
- @Parameter
- public String message;
-
- @Parameter(1)
- public List<Long> idsExpectedAfter101;
-
- @Parameter(2)
- public List<Long> idsExpectedAfter102;
-
- @Parameter(3)
- public List<Long> idsExpectedAfter103;
-
- @Parameter(4)
- public EnqueueLocation options;
-
- @Parameter(5)
- public List<FeedItem> queueInitial;
-
- @Parameter(6)
- public long idCurrentlyPlaying;
-
- @Test
- public void testQueueOrderWhenDownloading2Items() {
- ItemEnqueuePositionCalculator calculator = new ItemEnqueuePositionCalculator(options);
- try (MockedStatic<DownloadService> downloadServiceMock = Mockito.mockStatic(DownloadService.class)) {
- List<FeedItem> queue = new ArrayList<>(queueInitial);
-
- // Test body
- Playable currentlyPlaying = getCurrentlyPlaying(idCurrentlyPlaying);
- // User clicks download on feed item 101
- FeedItem feedItem101 = createFeedItem(101);
- downloadServiceMock.when(() ->
- DownloadService.isDownloadingFile(feedItem101.getMedia().getDownload_url())).thenReturn(true);
- doAddToQueueAndAssertResult(message + " (1st download)",
- calculator, feedItem101, queue, currentlyPlaying, idsExpectedAfter101);
- // Then user clicks download on feed item 102
- FeedItem feedItem102 = createFeedItem(102);
- downloadServiceMock.when(() ->
- DownloadService.isDownloadingFile(feedItem102.getMedia().getDownload_url())).thenReturn(true);
- doAddToQueueAndAssertResult(message + " (2nd download, it should preserve order of download)",
- calculator, feedItem102, queue, currentlyPlaying, idsExpectedAfter102);
- // simulate download failure case for 102
- downloadServiceMock.when(() ->
- DownloadService.isDownloadingFile(feedItem102.getMedia().getDownload_url())).thenReturn(false);
- // Then user clicks download on feed item 103
- FeedItem feedItem103 = createFeedItem(103);
- downloadServiceMock.when(() ->
- DownloadService.isDownloadingFile(feedItem103.getMedia().getDownload_url())).thenReturn(true);
- doAddToQueueAndAssertResult(message
- + " (3rd download, with 2nd download failed; "
- + "it should be behind 1st download (unless enqueueLocation is BACK)",
- calculator, feedItem103, queue, currentlyPlaying, idsExpectedAfter103);
- }
- }
- }
-
static void doAddToQueueAndAssertResult(String message,
ItemEnqueuePositionCalculator calculator,
FeedItem itemToAdd,