summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorH. Lehmann <ByteHamster@users.noreply.github.com>2019-10-31 09:23:47 +0100
committerGitHub <noreply@github.com>2019-10-31 09:23:47 +0100
commit2a2ced1631ebd9dc84f3e91339d2706b30a850b5 (patch)
tree23937c7821f3d551d684804259072fdebe3b1d55
parente7367e218d868967f7c1c45438c85c5c0958e56b (diff)
parent69f3a1210fcbd199cd24c4060da4518e0dda2146 (diff)
downloadAntennaPod-2a2ced1631ebd9dc84f3e91339d2706b30a850b5.zip
Merge pull request #3572 from ByteHamster/downloadservice-refactoring
DownloadService refactoring
-rw-r--r--app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java5
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DefaultDownloaderFactory.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java974
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java166
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderFactory.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java129
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java57
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java111
-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/storage/DBReader.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java6
18 files changed, 796 insertions, 801 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
index cb099cc85..3b5b35946 100644
--- a/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
@@ -5,6 +5,7 @@ import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import de.danoeh.antennapod.core.service.download.DownloaderFactory;
import org.awaitility.Awaitility;
import org.awaitility.core.ConditionTimeoutException;
import org.junit.After;
@@ -47,7 +48,7 @@ public class DownloadServiceTest {
private Feed testFeed = null;
private FeedMedia testMedia11 = null;
- private DownloadService.DownloaderFactory origFactory = null;
+ private DownloaderFactory origFactory = null;
@Before
public void setUp() throws Exception {
@@ -106,7 +107,7 @@ public class DownloadServiceTest {
});
}
- private static class StubDownloaderFactory implements DownloadService.DownloaderFactory {
+ private static class StubDownloaderFactory implements DownloaderFactory {
private final long downloadTime;
@NonNull
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
index 35a4014ba..f25159046 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
@@ -201,7 +201,7 @@ public class UITestUtils {
adapter.setCompleteFeed(hostedFeeds.toArray(new Feed[hostedFeeds.size()]));
adapter.setQueue(queue);
adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(hostedFeeds));
EventBus.getDefault().post(QueueEvent.setQueue(queue));
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
index 48c84344c..63851acd4 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -409,7 +409,9 @@ public class FeedItemlistFragment extends ListFragment {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onFeedListChanged(FeedListUpdateEvent event) {
- updateUi();
+ if (event.contains(feed)) {
+ updateUi();
+ }
}
private void updateProgressBarVisibility() {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
index 528fa7c32..7e8823c27 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
@@ -25,6 +25,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.view.EmptyViewHandler;
+import org.greenrobot.eventbus.ThreadMode;
/**
* Displays all running downloads and provides actions to cancel them
@@ -75,7 +76,7 @@ public class RunningDownloadsFragment extends ListFragment {
setListAdapter(null);
}
- @Subscribe(sticky = true)
+ @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(DownloadEvent event) {
Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]");
DownloaderUpdate update = event.update;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java
index 6073eb3bc..ca8db3cc9 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java
@@ -1,6 +1,28 @@
package de.danoeh.antennapod.core.event;
+import de.danoeh.antennapod.core.feed.Feed;
+
+import java.util.ArrayList;
+import java.util.List;
+
public class FeedListUpdateEvent {
- public FeedListUpdateEvent() {
+ private final List<Long> feeds = new ArrayList<>();
+
+ public FeedListUpdateEvent(List<Feed> feeds) {
+ for (Feed feed : feeds) {
+ this.feeds.add(feed.getId());
+ }
+ }
+
+ public FeedListUpdateEvent(Feed feed) {
+ feeds.add(feed.getId());
+ }
+
+ public FeedListUpdateEvent(long feedId) {
+ feeds.add(feedId);
+ }
+
+ public boolean contains(Feed feed) {
+ return feeds.contains(feed.getId());
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DefaultDownloaderFactory.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DefaultDownloaderFactory.java
new file mode 100644
index 000000000..c0de6c825
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DefaultDownloaderFactory.java
@@ -0,0 +1,20 @@
+package de.danoeh.antennapod.core.service.download;
+
+import android.util.Log;
+import android.webkit.URLUtil;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class DefaultDownloaderFactory implements DownloaderFactory {
+ private static final String TAG = "DefaultDwnldrFactory";
+
+ @Nullable
+ @Override
+ public Downloader create(@NonNull DownloadRequest request) {
+ if (!URLUtil.isHttpUrl(request.getSource()) && !URLUtil.isHttpsUrl(request.getSource())) {
+ Log.e(TAG, "Could not find appropriate downloader for " + request.getSource());
+ return null;
+ }
+ return new HttpDownloader(request);
+ }
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 296046031..95f78366f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -7,73 +7,48 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.media.MediaMetadataRetriever;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
-import android.webkit.URLUtil;
-
-import org.apache.commons.io.FileUtils;
-import org.greenrobot.eventbus.EventBus;
-import org.xml.sax.SAXException;
-
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.event.DownloadEvent;
+import de.danoeh.antennapod.core.event.FeedItemEvent;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.GpodnetSyncService;
+import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
+import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
+import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
+import de.danoeh.antennapod.core.service.download.handler.PostDownloaderTask;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.DownloadError;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.event.DownloadEvent;
-import de.danoeh.antennapod.core.event.FeedItemEvent;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.feed.FeedPreferences;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.GpodnetSyncService;
-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.DownloadRequestException;
-import de.danoeh.antennapod.core.storage.DownloadRequester;
-import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
-import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
-import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
-import de.danoeh.antennapod.core.util.ChapterUtils;
-import de.danoeh.antennapod.core.util.DownloadError;
-import de.danoeh.antennapod.core.util.InvalidFeedException;
-import de.danoeh.antennapod.core.util.gui.NotificationUtils;
+import org.apache.commons.io.FileUtils;
+import org.greenrobot.eventbus.EventBus;
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued via the startService intent.
@@ -106,26 +81,21 @@ public class DownloadService extends Service {
*/
public static final String EXTRA_REQUEST = "request";
+ public static final int NOTIFICATION_ID = 2;
+
/**
* Contains all completed downloads that have not been included in the report yet.
*/
- private List<DownloadStatus> reportQueue;
-
- private ExecutorService syncExecutor;
- private CompletionService<Downloader> downloadExecutor;
- private FeedSyncThread feedSyncThread;
-
- private DownloadRequester requester;
-
-
- private NotificationCompat.Builder notificationCompatBuilder;
- private static final int NOTIFICATION_ID = 2;
- private static final int REPORT_ID = 3;
+ private final List<DownloadStatus> reportQueue;
+ private final ExecutorService syncExecutor;
+ private final CompletionService<Downloader> downloadExecutor;
+ private final DownloadRequester requester;
+ private DownloadServiceNotification notificationManager;
/**
* Currently running downloads.
*/
- private List<Downloader> downloads;
+ private final List<Downloader> downloads;
/**
* Number of running downloads.
@@ -141,10 +111,10 @@ public class DownloadService extends Service {
private NotificationUpdater notificationUpdater;
private ScheduledFuture<?> notificationUpdaterFuture;
+ private ScheduledFuture<?> downloadPostFuture;
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
-
- private final Handler postHandler = new Handler();
+ private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
private final IBinder mBinder = new LocalBinder();
@@ -154,108 +124,12 @@ public class DownloadService extends Service {
}
}
- private final Thread downloadCompletionThread = new Thread("DownloadCompletionThread") {
- private static final String TAG = "downloadCompletionThd";
-
- @Override
- public void run() {
- Log.d(TAG, "downloadCompletionThread was started");
- while (!isInterrupted()) {
- try {
- Downloader downloader = downloadExecutor.take().get();
- Log.d(TAG, "Received 'Download Complete' - message.");
- removeDownload(downloader);
- DownloadStatus status = downloader.getResult();
- boolean successful = status.isSuccessful();
-
- final int type = status.getFeedfileType();
- if (successful) {
- if (type == Feed.FEEDFILETYPE_FEED) {
- handleCompletedFeedDownload(downloader.getDownloadRequest());
- } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
- }
- } else {
- numberOfDownloads.decrementAndGet();
- if (!status.isCancelled()) {
- if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
- 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()));
- DownloadRequester.getInstance().download(DownloadService.this, downloader.getDownloadRequest());
- } else {
- Log.e(TAG, "Download failed");
- saveDownloadStatus(status);
- handleFailedDownload(status, downloader.getDownloadRequest());
-
- if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- FeedItem item = getFeedItemFromId(status.getFeedfileId());
- if (item == null) {
- return;
- }
- boolean httpNotFound = status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
- && String.valueOf(HttpURLConnection.HTTP_NOT_FOUND).equals(status.getReasonDetailed());
- boolean forbidden = status.getReason() == DownloadError.ERROR_FORBIDDEN
- && String.valueOf(HttpURLConnection.HTTP_FORBIDDEN).equals(status.getReasonDetailed());
- boolean notEnoughSpace = status.getReason() == DownloadError.ERROR_NOT_ENOUGH_SPACE;
- boolean wrongFileType = status.getReason() == DownloadError.ERROR_FILE_TYPE;
- if (httpNotFound || forbidden || notEnoughSpace || wrongFileType) {
- DBWriter.saveFeedItemAutoDownloadFailed(item).get();
- }
- // 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));
- }
- }
- queryDownloadsAsync();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "DownloadCompletionThread was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in DownloadCompletionThread: " + e.getMessage());
- numberOfDownloads.decrementAndGet();
- }
- }
- Log.d(TAG, "End of downloadCompletionThread");
- }
- };
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
- onDownloadQueued(intent);
- } else if (numberOfDownloads.get() == 0) {
- stopSelf();
- }
- return Service.START_NOT_STICKY;
- }
-
- @Override
- public void onCreate() {
- Log.d(TAG, "Service started");
- isRunning = true;
- handler = new Handler();
+ public DownloadService() {
reportQueue = Collections.synchronizedList(new ArrayList<>());
downloads = Collections.synchronizedList(new ArrayList<>());
numberOfDownloads = new AtomicInteger(0);
+ requester = DownloadRequester.getInstance();
- IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
- registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
syncExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
@@ -278,13 +152,35 @@ public class DownloadService extends Service {
return t;
}, (r, executor) -> Log.w(TAG, "SchedEx rejected submission of new task")
);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
+ onDownloadQueued(intent);
+ } else if (numberOfDownloads.get() == 0) {
+ stopSelf();
+ }
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "Service started");
+ isRunning = true;
+ handler = new Handler();
+ notificationManager = new DownloadServiceNotification(this);
+
+ IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
+ registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
+
downloadCompletionThread.start();
- feedSyncThread = new FeedSyncThread();
- feedSyncThread.start();
- setupNotificationBuilders();
- requester = DownloadRequester.getInstance();
- startForeground(NOTIFICATION_ID, updateNotifications());
+ Notification notification = notificationManager.updateNotifications(
+ requester.getNumberOfDownloads(), downloads);
+ startForeground(NOTIFICATION_ID, notification);
}
@Override
@@ -297,12 +193,12 @@ public class DownloadService extends Service {
Log.d(TAG, "Service shutting down");
isRunning = false;
- if (ClientConfig.downloadServiceCallbacks.shouldCreateReport() &&
- UserPreferences.showDownloadReport()) {
- updateReport();
+ if (ClientConfig.downloadServiceCallbacks.shouldCreateReport()
+ && UserPreferences.showDownloadReport()) {
+ notificationManager.updateReport(reportQueue);
+ reportQueue.clear();
}
- postHandler.removeCallbacks(postDownloaderTask);
EventBus.getDefault().postSticky(DownloadEvent.refresh(Collections.emptyList()));
stopForeground(true);
@@ -312,8 +208,8 @@ public class DownloadService extends Service {
downloadCompletionThread.interrupt();
syncExecutor.shutdown();
schedExecutor.shutdown();
- feedSyncThread.shutdown();
cancelNotificationUpdater();
+ downloadPostFuture.cancel(true);
unregisterReceiver(cancelDownloadReceiver);
// if this was the initial gpodder sync, i.e. we just synced the feeds successfully,
@@ -328,38 +224,120 @@ public class DownloadService extends Service {
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
- private void setupNotificationBuilders() {
- notificationCompatBuilder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOADING)
- .setOngoing(true)
- .setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
- .setSmallIcon(R.drawable.stat_notify_sync);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- notificationCompatBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
+ private final Thread downloadCompletionThread = new Thread("DownloadCompletionThread") {
+ private static final String TAG = "downloadCompletionThd";
+
+ @Override
+ public void run() {
+ Log.d(TAG, "downloadCompletionThread was started");
+ while (!isInterrupted()) {
+ try {
+ Downloader downloader = downloadExecutor.take().get();
+ Log.d(TAG, "Received 'Download Complete' - message.");
+
+ if (downloader.getResult().isSuccessful()) {
+ syncExecutor.execute(() -> {
+ handleSuccessfulDownload(downloader);
+ removeDownload(downloader);
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+
+ });
+ } else {
+ handleFailedDownload(downloader);
+ removeDownload(downloader);
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "DownloadCompletionThread was interrupted");
+ } catch (ExecutionException e) {
+ Log.e(TAG, "ExecutionException in DownloadCompletionThread: " + e.getMessage());
+ }
+ }
+ Log.d(TAG, "End of downloadCompletionThread");
}
+ };
- Log.d(TAG, "Notification set up");
- }
+ private void handleSuccessfulDownload(Downloader downloader) {
+ DownloadRequest request = downloader.getDownloadRequest();
+ DownloadStatus status = downloader.getResult();
+ final int type = status.getFeedfileType();
- /**
- * Updates the contents of the service's notifications. Should be called
- * after setupNotificationBuilders.
- */
- private Notification updateNotifications() {
- if (notificationCompatBuilder == null) {
- return null;
+ if (type == Feed.FEEDFILETYPE_FEED) {
+ Log.d(TAG, "Handling completed Feed Download");
+ FeedSyncTask task = new FeedSyncTask(DownloadService.this, request);
+ boolean success = task.run();
+
+ if (success) {
+ // we create a 'successful' download log if the feed's last refresh failed
+ List<DownloadStatus> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
+ if (log.size() > 0 && !log.get(0).isSuccessful()) {
+ saveDownloadStatus(task.getDownloadStatus());
+ }
+ } else {
+ saveDownloadStatus(task.getDownloadStatus());
+ }
+ } else 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());
}
+ }
+
+ private void handleFailedDownload(Downloader downloader) {
+ DownloadStatus status = downloader.getResult();
+ final int type = status.getFeedfileType();
- String contentTitle = getString(R.string.download_notification_title);
- int numDownloads = requester.getNumberOfDownloads();
- String downloadsLeft = (numDownloads > 0) ?
- getResources().getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads) :
- getString(R.string.downloads_processing);
- String bigText = compileNotificationString(downloads);
-
- notificationCompatBuilder.setContentTitle(contentTitle);
- notificationCompatBuilder.setContentText(downloadsLeft);
- notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
- return notificationCompatBuilder.build();
+ 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()));
+ DownloadRequester.getInstance().download(DownloadService.this, downloader.getDownloadRequest());
+ } else {
+ Log.e(TAG, "Download failed");
+ saveDownloadStatus(status);
+ syncExecutor.execute(new FailedDownloadHandler(downloader.getDownloadRequest()));
+
+ if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ FeedItem item = getFeedItemFromId(status.getFeedfileId());
+ if (item == null) {
+ return;
+ }
+ boolean httpNotFound = status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
+ && String.valueOf(HttpURLConnection.HTTP_NOT_FOUND).equals(status.getReasonDetailed());
+ boolean forbidden = status.getReason() == DownloadError.ERROR_FORBIDDEN
+ && String.valueOf(HttpURLConnection.HTTP_FORBIDDEN).equals(status.getReasonDetailed());
+ boolean notEnoughSpace = status.getReason() == DownloadError.ERROR_NOT_ENOUGH_SPACE;
+ boolean wrongFileType = status.getReason() == DownloadError.ERROR_FILE_TYPE;
+ if (httpNotFound || forbidden || notEnoughSpace || wrongFileType) {
+ try {
+ DBWriter.saveFeedItemAutoDownloadFailed(item).get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.d(TAG, "Ignoring exception while setting item download status");
+ e.printStackTrace();
+ }
+ }
+ // 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 Downloader getDownloader(String downloadUrl) {
@@ -418,7 +396,7 @@ public class DownloadService extends Service {
writeFileUrl(request);
- Downloader downloader = getDownloader(request);
+ Downloader downloader = downloaderFactory.create(request);
if (downloader != null) {
numberOfDownloads.incrementAndGet();
// smaller rss feeds before bigger media files
@@ -436,26 +414,6 @@ public class DownloadService extends Service {
}
@VisibleForTesting
- public interface DownloaderFactory {
- @Nullable
- Downloader create(@NonNull DownloadRequest request);
- }
-
- private static class DefaultDownloaderFactory implements DownloaderFactory {
- @Nullable
- @Override
- public Downloader create(@NonNull DownloadRequest request) {
- if (!URLUtil.isHttpUrl(request.getSource()) && !URLUtil.isHttpsUrl(request.getSource())) {
- Log.e(TAG, "Could not find appropriate downloader for " + request.getSource());
- return null;
- }
- return new HttpDownloader(request);
- }
- }
-
- private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
-
- @VisibleForTesting
public static DownloaderFactory getDownloaderFactory() {
return downloaderFactory;
}
@@ -467,10 +425,6 @@ public class DownloadService extends Service {
DownloadService.downloaderFactory = downloaderFactory;
}
- private Downloader getDownloader(@NonNull DownloadRequest request) {
- return downloaderFactory.create(request);
- }
-
/**
* Remove download from the DownloadRequester list and from the
* DownloadService list.
@@ -497,55 +451,6 @@ public class DownloadService extends Service {
}
/**
- * 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
- */
- private void updateReport() {
- // check if report should be created
- boolean createReport = false;
- int successfulDownloads = 0;
- 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.isSuccessful()) {
- successfulDownloads++;
- } else if (!status.isCancelled()) {
- createReport = true;
- failedDownloads++;
- }
- }
-
- if (createReport) {
- Log.d(TAG, "Creating report");
- // create notification object
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
- .setTicker(getString(R.string.download_report_title))
- .setContentTitle(getString(R.string.download_report_content_title))
- .setContentText(
- String.format(
- getString(R.string.download_report_content),
- successfulDownloads, failedDownloads)
- )
- .setSmallIcon(R.drawable.stat_notify_sync_error)
- .setContentIntent(
- ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this)
- )
- .setAutoCancel(true);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- builder.setVisibility(Notification.VISIBILITY_PUBLIC);
- }
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(REPORT_ID, builder.build());
- } else {
- Log.d(TAG, "No report is created");
- }
- reportQueue.clear();
- }
-
- /**
* Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is
* used from a thread other than the main thread.
*/
@@ -564,53 +469,12 @@ public class DownloadService extends Service {
stopSelf();
} else {
setupNotificationUpdater();
- startForeground(NOTIFICATION_ID, updateNotifications());
+ Notification notification = notificationManager.updateNotifications(
+ requester.getNumberOfDownloads(), downloads);
+ startForeground(NOTIFICATION_ID, notification);
}
}
- private void postAuthenticationNotification(final DownloadRequest downloadRequest) {
- handler.post(() -> {
- final String resourceTitle = (downloadRequest.getTitle() != null) ?
- downloadRequest.getTitle() : downloadRequest.getSource();
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this, NotificationUtils.CHANNEL_ID_USER_ACTION);
- builder.setTicker(getText(R.string.authentication_notification_title))
- .setContentTitle(getText(R.string.authentication_notification_title))
- .setContentText(getText(R.string.authentication_notification_msg))
- .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg)
- + ": " + resourceTitle))
- .setSmallIcon(R.drawable.ic_notification_key)
- .setAutoCancel(true)
- .setContentIntent(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(DownloadService.this, downloadRequest));
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- builder.setVisibility(Notification.VISIBILITY_PUBLIC);
- }
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(downloadRequest.getSource().hashCode(), builder.build());
- });
- }
-
- /**
- * Is called whenever a Feed is downloaded
- */
- private void handleCompletedFeedDownload(DownloadRequest request) {
- Log.d(TAG, "Handling completed Feed Download");
- feedSyncThread.submitCompletedDownload(request);
- }
-
- /**
- * Is called whenever a FeedMedia is downloaded.
- */
- private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
- Log.d(TAG, "Handling completed FeedMedia Download");
- syncExecutor.execute(new MediaHandlerThread(status, request));
- }
-
- private void handleFailedDownload(DownloadStatus status, DownloadRequest request) {
- Log.d(TAG, "Handling failed download");
- syncExecutor.execute(new FailedDownloadHandler(status, request));
- }
-
@Nullable
private FeedItem getFeedItemFromId(long id) {
FeedMedia media = DBReader.getFeedMedia(id);
@@ -622,300 +486,6 @@ public class DownloadService extends Service {
}
/**
- * Takes a single Feed, parses the corresponding file and refreshes
- * information in the manager
- */
- private class FeedSyncThread extends Thread {
- private static final String TAG = "FeedSyncThread";
-
- private final BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<>();
- private final CompletionService<Pair<DownloadRequest, FeedHandlerResult>> parserService = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor());
- private final ExecutorService dbService = Executors.newSingleThreadExecutor();
- private Future<?> dbUpdateFuture;
- private volatile boolean isActive = true;
- private volatile boolean isCollectingRequests = false;
-
- private static final long WAIT_TIMEOUT = 3000;
-
- FeedSyncThread() {
- super("FeedSyncThread");
- }
-
- /**
- * Waits for completed requests. Once the first request has been taken, the method will wait WAIT_TIMEOUT ms longer to
- * collect more completed requests.
- *
- * @return Collected feeds or null if the method has been interrupted during the first waiting period.
- */
- private List<Pair<DownloadRequest, FeedHandlerResult>> collectCompletedRequests() {
- List<Pair<DownloadRequest, FeedHandlerResult>> results = new LinkedList<>();
- DownloadRequester requester = DownloadRequester.getInstance();
- int tasks = 0;
-
- try {
- DownloadRequest request = completedRequests.take();
- parserService.submit(new FeedParserTask(request));
- tasks++;
- } catch (InterruptedException e) {
- Log.e(TAG, "FeedSyncThread was interrupted");
- return null;
- }
-
- tasks += pollCompletedDownloads();
-
- isCollectingRequests = true;
-
- if (requester.isDownloadingFeeds()) {
- // wait for completion of more downloads
- long startTime = System.currentTimeMillis();
- long currentTime = startTime;
- while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) {
- try {
- Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms");
- sleep(startTime + WAIT_TIMEOUT - currentTime);
- } catch (InterruptedException e) {
- Log.d(TAG, "interrupted while waiting for more downloads");
- tasks += pollCompletedDownloads();
- } finally {
- currentTime = System.currentTimeMillis();
- }
- }
-
- tasks += pollCompletedDownloads();
-
- }
-
- isCollectingRequests = false;
-
- for (int i = 0; i < tasks; i++) {
- try {
- Pair<DownloadRequest, FeedHandlerResult> result = parserService.take().get();
- if (result != null) {
- results.add(result);
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "FeedSyncThread was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- return results;
- }
-
- private int pollCompletedDownloads() {
- int tasks = 0;
- while (!completedRequests.isEmpty()) {
- parserService.submit(new FeedParserTask(completedRequests.poll()));
- tasks++;
- }
- return tasks;
- }
-
- @Override
- public void run() {
- while (isActive) {
- final List<Pair<DownloadRequest, FeedHandlerResult>> results = collectCompletedRequests();
-
- if (results == null) {
- continue;
- }
-
- Log.d(TAG, "Bundling " + results.size() + " feeds");
-
- // Save information of feed in DB
- if (dbUpdateFuture != null) {
- try {
- dbUpdateFuture.get();
- } catch (InterruptedException e) {
- Log.e(TAG, "FeedSyncThread was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- dbUpdateFuture = dbService.submit(() -> {
- Feed[] savedFeeds = DBTasks.updateFeed(DownloadService.this, getFeeds(results));
-
- for (int i = 0; i < savedFeeds.length; i++) {
- Feed savedFeed = savedFeeds[i];
-
- // If loadAllPages=true, check if another page is available and queue it for download
- final boolean loadAllPages = results.get(i).first.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
- final Feed feed = results.get(i).second.feed;
- if (loadAllPages && feed.getNextPageLink() != null) {
- try {
- feed.setId(savedFeed.getId());
- DBTasks.loadNextPageOfFeed(DownloadService.this, savedFeed, true);
- } catch (DownloadRequestException e) {
- Log.e(TAG, "Error trying to load next page", e);
- }
- }
-
- ClientConfig.downloadServiceCallbacks.onFeedParsed(DownloadService.this,
- savedFeed);
-
- numberOfDownloads.decrementAndGet();
- }
-
- queryDownloadsAsync();
- });
-
- }
-
- if (dbUpdateFuture != null) {
- try {
- dbUpdateFuture.get();
- } catch (InterruptedException e) {
- Log.e(TAG, "interrupted while updating the db");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException while updating the db: " + e.getMessage());
- }
- }
-
- Log.d(TAG, "Shutting down");
- }
-
- /**
- * Helper method
- */
- private Feed[] getFeeds(List<Pair<DownloadRequest, FeedHandlerResult>> results) {
- Feed[] feeds = new Feed[results.size()];
- for (int i = 0; i < results.size(); i++) {
- feeds[i] = results.get(i).second.feed;
- }
- return feeds;
- }
-
- private class FeedParserTask implements Callable<Pair<DownloadRequest, FeedHandlerResult>> {
-
- private final DownloadRequest request;
-
- private FeedParserTask(DownloadRequest request) {
- this.request = request;
- }
-
- @Override
- public Pair<DownloadRequest, FeedHandlerResult> call() throws Exception {
- return parseFeed(request);
- }
- }
-
- private Pair<DownloadRequest, FeedHandlerResult> parseFeed(DownloadRequest request) {
- Feed feed = new Feed(request.getSource(), request.getLastModified());
- feed.setFile_url(request.getDestination());
- feed.setId(request.getFeedfileId());
- feed.setDownloaded(true);
- feed.setPreferences(new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL,
- request.getUsername(), request.getPassword()));
- feed.setPageNr(request.getArguments().getInt(DownloadRequester.REQUEST_ARG_PAGE_NR, 0));
-
- DownloadError reason = null;
- String reasonDetailed = null;
- boolean successful = true;
- FeedHandler feedHandler = new FeedHandler();
-
- FeedHandlerResult result = null;
- try {
- result = feedHandler.parseFeed(feed);
- Log.d(TAG, feed.getTitle() + " parsed");
- if (!checkFeedData(feed)) {
- throw new InvalidFeedException();
- }
-
- } catch (SAXException | IOException | ParserConfigurationException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (UnsupportedFeedtypeException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
- reasonDetailed = e.getMessage();
- } catch (InvalidFeedException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } finally {
- File feedFile = new File(request.getDestination());
- if (feedFile.exists()) {
- boolean deleted = feedFile.delete();
- Log.d(TAG, "Deletion of file '" + feedFile.getAbsolutePath() + "' " + (deleted ? "successful" : "FAILED"));
- }
- }
-
- if (successful) {
- // we create a 'successful' download log if the feed's last refresh failed
- List<DownloadStatus> log = DBReader.getFeedDownloadLog(feed);
- if (log.size() > 0 && !log.get(0).isSuccessful()) {
- saveDownloadStatus(
- new DownloadStatus(feed, feed.getHumanReadableIdentifier(),
- DownloadError.SUCCESS, successful, reasonDetailed));
- }
- return Pair.create(request, result);
- } else {
- numberOfDownloads.decrementAndGet();
- saveDownloadStatus(
- new DownloadStatus(feed, feed.getHumanReadableIdentifier(), reason,
- successful, reasonDetailed));
- return null;
- }
- }
-
-
- /**
- * Checks if the feed was parsed correctly.
- */
- private boolean checkFeedData(Feed feed) {
- if (feed.getTitle() == null) {
- Log.e(TAG, "Feed has no title.");
- return false;
- }
- if (!hasValidFeedItems(feed)) {
- Log.e(TAG, "Feed has invalid items");
- return false;
- }
- return true;
- }
-
- private boolean hasValidFeedItems(Feed feed) {
- for (FeedItem item : feed.getItems()) {
- if (item.getTitle() == null) {
- Log.e(TAG, "Item has no title");
- return false;
- }
- if (item.getPubDate() == null) {
- Log.e(TAG, "Item has no pubDate. Using current time as pubDate");
- if (item.getTitle() != null) {
- Log.e(TAG, "Title of invalid item: " + item.getTitle());
- }
- item.setPubDate(new Date());
- }
- }
- return true;
- }
-
- public void shutdown() {
- isActive = false;
- if (isCollectingRequests) {
- interrupt();
- }
- }
-
- void submitCompletedDownload(DownloadRequest request) {
- completedRequests.offer(request);
- if (isCollectingRequests) {
- interrupt();
- }
- }
-
- }
-
- /**
* 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.
*/
@@ -952,128 +522,13 @@ public class DownloadService extends Service {
}
/**
- * 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.
- */
- private static class FailedDownloadHandler implements Runnable {
-
- private final DownloadRequest request;
- private final DownloadStatus status;
-
- FailedDownloadHandler(DownloadStatus status, DownloadRequest request) {
- this.request = request;
- this.status = status;
- }
-
- @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");
- }
- }
- }
-
- /**
- * Handles a completed media download.
- */
- private class MediaHandlerThread implements Runnable {
-
- private final DownloadRequest request;
- private DownloadStatus status;
-
- MediaHandlerThread(@NonNull DownloadStatus status,
- @NonNull DownloadRequest request) {
- this.status = status;
- this.request = request;
- }
-
- @Override
- public void run() {
- FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
- if (media == null) {
- Log.e(TAG, "Could not find downloaded media object in database");
- return;
- }
- media.setDownloaded(true);
- media.setFile_url(request.getDestination());
- media.checkEmbeddedPicture(); // enforce check
-
- // check if file has chapters
- if(media.getItem() != null && !media.getItem().hasChapters()) {
- ChapterUtils.loadChaptersFromFileUrl(media);
- }
-
- // Get duration
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- String durationStr = null;
- try {
- mmr.setDataSource(media.getFile_url());
- durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
- media.setDuration(Integer.parseInt(durationStr));
- Log.d(TAG, "Duration of file is " + media.getDuration());
- } catch (NumberFormatException e) {
- Log.d(TAG, "Invalid file duration: " + durationStr);
- } catch (Exception e) {
- Log.e(TAG, "Get duration failed", e);
- } finally {
- mmr.release();
- }
-
- final FeedItem item = media.getItem();
-
- try {
- DBWriter.setFeedMedia(media).get();
-
- // we've received the media, we don't want to autodownload it again
- if (item != null) {
- item.setAutoDownload(false);
- // setFeedItem() signals (via EventBus) that the item has been updated,
- // so we do it after the enclosing media has been updated above,
- // to ensure subscribers will get the updated FeedMedia as well
- DBWriter.setFeedItem(item).get();
- }
-
- if (item != null && UserPreferences.enqueueDownloadedEpisodes() &&
- !DBTasks.isInQueue(DownloadService.this, item.getId())) {
- DBWriter.addQueueItem(DownloadService.this, item).get();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "MediaHandlerThread was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in MediaHandlerThread: " + e.getMessage());
- status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
- }
-
- saveDownloadStatus(status);
-
- if (GpodnetPreferences.loggedIn() && item != null) {
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.DOWNLOAD)
- .currentDeviceId()
- .currentTimestamp()
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
- }
-
- numberOfDownloads.decrementAndGet();
- queryDownloadsAsync();
- }
- }
-
- /**
* Schedules the notification updater task if it hasn't been scheduled yet.
*/
private void setupNotificationUpdater() {
Log.d(TAG, "Setting up notification updater");
if (notificationUpdater == null) {
notificationUpdater = new NotificationUpdater();
- notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
- notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
+ notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(notificationUpdater, 1, 1, TimeUnit.SECONDS);
}
}
@@ -1089,71 +544,20 @@ public class DownloadService extends Service {
private class NotificationUpdater implements Runnable {
public void run() {
- handler.post(() -> {
- Notification n = updateNotifications();
- if (n != null) {
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, n);
- }
- });
- }
-
- }
-
-
- private long lastPost = 0;
-
- private final Runnable postDownloaderTask = new Runnable() {
- @Override
- public void run() {
- List<Downloader> runningDownloads = new ArrayList<>();
- for (Downloader downloader : downloads) {
- if (!downloader.cancelled) {
- runningDownloads.add(downloader);
- }
+ Notification n = notificationManager.updateNotifications(requester.getNumberOfDownloads(), downloads);
+ if (n != null) {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, n);
}
- List<Downloader> list = Collections.unmodifiableList(runningDownloads);
- EventBus.getDefault().postSticky(DownloadEvent.refresh(list));
- postHandler.postDelayed(postDownloaderTask, 1500);
}
- };
+ }
private void postDownloaders() {
- long now = System.currentTimeMillis();
- if (now - lastPost >= 250) {
- postHandler.removeCallbacks(postDownloaderTask);
- postDownloaderTask.run();
- lastPost = now;
- }
- }
+ new PostDownloaderTask(downloads).run();
- private static String compileNotificationString(List<Downloader> downloads) {
- List<String> lines = new ArrayList<>(downloads.size());
- for (Downloader downloader : downloads) {
- if (downloader.cancelled) {
- continue;
- }
- StringBuilder line = new StringBuilder("• ");
- DownloadRequest request = downloader.getDownloadRequest();
- switch (request.getFeedfileType()) {
- case Feed.FEEDFILETYPE_FEED:
- if (request.getTitle() != null) {
- line.append(request.getTitle());
- }
- break;
- case FeedMedia.FEEDFILETYPE_FEEDMEDIA:
- if (request.getTitle() != null) {
- line.append(request.getTitle())
- .append(" (")
- .append(request.getProgressPercent())
- .append("%)");
- }
- break;
- default:
- line.append("Unknown: ").append(request.getFeedfileType());
- }
- lines.add(line.toString());
+ if (downloadPostFuture == null) {
+ downloadPostFuture = schedExecutor.scheduleAtFixedRate(
+ new PostDownloaderTask(downloads), 1, 1, TimeUnit.SECONDS);
}
- return TextUtils.join("\n", lines);
}
}
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
new file mode 100644
index 000000000..431eccc8c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
@@ -0,0 +1,166 @@
+package de.danoeh.antennapod.core.service.download;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import androidx.core.app.NotificationCompat;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class DownloadServiceNotification {
+ private static final String TAG = "DownloadSvcNotification";
+ private static final int REPORT_ID = 3;
+
+ 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(true)
+ .setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(context))
+ .setSmallIcon(R.drawable.stat_notify_sync);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ notificationCompatBuilder.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(int numDownloads, List<Downloader> downloads) {
+ if (notificationCompatBuilder == null) {
+ return null;
+ }
+
+ String contentTitle = context.getString(R.string.download_notification_title);
+ String downloadsLeft = (numDownloads > 0)
+ ? context.getResources().getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads)
+ : context.getString(R.string.downloads_processing);
+ String bigText = compileNotificationString(downloads);
+
+ notificationCompatBuilder.setContentTitle(contentTitle);
+ notificationCompatBuilder.setContentText(downloadsLeft);
+ notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
+ return notificationCompatBuilder.build();
+ }
+
+ private static String compileNotificationString(List<Downloader> downloads) {
+ List<String> lines = new ArrayList<>(downloads.size());
+ for (Downloader downloader : downloads) {
+ if (downloader.cancelled) {
+ continue;
+ }
+ StringBuilder line = new StringBuilder("• ");
+ DownloadRequest request = downloader.getDownloadRequest();
+ switch (request.getFeedfileType()) {
+ case Feed.FEEDFILETYPE_FEED:
+ if (request.getTitle() != null) {
+ line.append(request.getTitle());
+ }
+ break;
+ case FeedMedia.FEEDFILETYPE_FEEDMEDIA:
+ if (request.getTitle() != null) {
+ line.append(request.getTitle())
+ .append(" (")
+ .append(request.getProgressPercent())
+ .append("%)");
+ }
+ break;
+ default:
+ line.append("Unknown: ").append(request.getFeedfileType());
+ }
+ lines.add(line.toString());
+ }
+ return TextUtils.join("\n", lines);
+ }
+
+ /**
+ * 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) {
+ // check if report should be created
+ boolean createReport = false;
+ int successfulDownloads = 0;
+ 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.isSuccessful()) {
+ successfulDownloads++;
+ } else if (!status.isCancelled()) {
+ createReport = true;
+ failedDownloads++;
+ }
+ }
+
+ if (createReport) {
+ Log.d(TAG, "Creating report");
+ // create notification object
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context,
+ NotificationUtils.CHANNEL_ID_ERROR)
+ .setTicker(context.getString(R.string.download_report_title))
+ .setContentTitle(context.getString(R.string.download_report_content_title))
+ .setContentText(
+ String.format(
+ context.getString(R.string.download_report_content),
+ successfulDownloads, failedDownloads)
+ )
+ .setSmallIcon(R.drawable.stat_notify_sync_error)
+ .setContentIntent(
+ ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(context)
+ )
+ .setAutoCancel(true);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ }
+ NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(REPORT_ID, builder.build());
+ } else {
+ Log.d(TAG, "No report is created");
+ }
+ }
+
+ 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(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(context, downloadRequest));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ }
+ NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(downloadRequest.getSource().hashCode(), builder.build());
+ }
+}
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 02dc17301..2a0989d23 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
@@ -17,7 +17,7 @@ public abstract class Downloader implements Callable<Downloader> {
private volatile boolean finished;
- volatile boolean cancelled;
+ public volatile boolean cancelled;
@NonNull
final DownloadRequest request;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderFactory.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderFactory.java
new file mode 100644
index 000000000..d96210a6e
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderFactory.java
@@ -0,0 +1,9 @@
+package de.danoeh.antennapod.core.service.download;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public interface DownloaderFactory {
+ @Nullable
+ Downloader create(@NonNull DownloadRequest request);
+} \ 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
new file mode 100644
index 000000000..041d26bd4
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
@@ -0,0 +1,33 @@
+package de.danoeh.antennapod.core.service.download.handler;
+
+import android.util.Log;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+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
new file mode 100644
index 000000000..10d5bfa15
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
@@ -0,0 +1,129 @@
+package de.danoeh.antennapod.core.service.download.handler;
+
+import android.util.Log;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
+import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
+import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
+import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.InvalidFeedException;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+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 boolean successful = true;
+
+ public FeedParserTask(DownloadRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public FeedHandlerResult call() {
+ Feed feed = new Feed(request.getSource(), request.getLastModified());
+ feed.setFile_url(request.getDestination());
+ feed.setId(request.getFeedfileId());
+ feed.setDownloaded(true);
+ feed.setPreferences(new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL,
+ request.getUsername(), request.getPassword()));
+ feed.setPageNr(request.getArguments().getInt(DownloadRequester.REQUEST_ARG_PAGE_NR, 0));
+
+ DownloadError reason = null;
+ String reasonDetailed = null;
+ FeedHandler feedHandler = new FeedHandler();
+
+ FeedHandlerResult result = null;
+ try {
+ result = feedHandler.parseFeed(feed);
+ Log.d(TAG, feed.getTitle() + " parsed");
+ if (!checkFeedData(feed)) {
+ throw new InvalidFeedException();
+ }
+
+ } catch (SAXException | IOException | ParserConfigurationException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (UnsupportedFeedtypeException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
+ reasonDetailed = e.getMessage();
+ } catch (InvalidFeedException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } finally {
+ File feedFile = new File(request.getDestination());
+ if (feedFile.exists()) {
+ boolean deleted = feedFile.delete();
+ Log.d(TAG, "Deletion of file '" + feedFile.getAbsolutePath() + "' "
+ + (deleted ? "successful" : "FAILED"));
+ }
+ }
+
+ if (successful) {
+ downloadStatus = new DownloadStatus(feed, feed.getHumanReadableIdentifier(),
+ DownloadError.SUCCESS, successful, reasonDetailed);
+ return result;
+ } else {
+ downloadStatus = new DownloadStatus(feed, feed.getHumanReadableIdentifier(),
+ reason, successful, reasonDetailed);
+ return null;
+ }
+ }
+
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ /**
+ * Checks if the feed was parsed correctly.
+ */
+ private boolean checkFeedData(Feed feed) {
+ if (feed.getTitle() == null) {
+ Log.e(TAG, "Feed has no title.");
+ return false;
+ }
+ if (!hasValidFeedItems(feed)) {
+ Log.e(TAG, "Feed has invalid items");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean hasValidFeedItems(Feed feed) {
+ for (FeedItem item : feed.getItems()) {
+ if (item.getTitle() == null) {
+ Log.e(TAG, "Item has no title");
+ return false;
+ }
+ if (item.getPubDate() == null) {
+ Log.e(TAG, "Item has no pubDate. Using current time as pubDate");
+ if (item.getTitle() != null) {
+ Log.e(TAG, "Title of invalid item: " + item.getTitle());
+ }
+ item.setPubDate(new Date());
+ }
+ }
+ return true;
+ }
+
+ public DownloadStatus getDownloadStatus() {
+ return downloadStatus;
+ }
+}
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
new file mode 100644
index 000000000..718faaa0a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java
@@ -0,0 +1,57 @@
+package de.danoeh.antennapod.core.service.download.handler;
+
+import android.content.Context;
+import android.util.Log;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
+import java.util.List;
+
+public class FeedSyncTask {
+ private static final String TAG = "FeedParserTask";
+ private final DownloadRequest request;
+ private final Context context;
+ private DownloadStatus downloadStatus;
+
+ public FeedSyncTask(Context context, DownloadRequest request) {
+ this.request = request;
+ this.context = context;
+ }
+
+ public boolean run() {
+ FeedParserTask task = new FeedParserTask(request);
+ FeedHandlerResult result = task.call();
+ downloadStatus = task.getDownloadStatus();
+
+ if (!task.isSuccessful()) {
+ return false;
+ }
+
+ Feed[] savedFeeds = DBTasks.updateFeed(context, result.feed);
+ Feed savedFeed = savedFeeds[0];
+ // If loadAllPages=true, check if another page is available and queue it for download
+ final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
+ final Feed feed = result.feed;
+ if (loadAllPages && feed.getNextPageLink() != null) {
+ try {
+ feed.setId(savedFeed.getId());
+ DBTasks.loadNextPageOfFeed(context, savedFeed, true);
+ } catch (DownloadRequestException e) {
+ Log.e(TAG, "Error trying to load next page", e);
+ }
+ }
+
+ ClientConfig.downloadServiceCallbacks.onFeedParsed(context, savedFeed);
+ return true;
+ }
+
+ public DownloadStatus getDownloadStatus() {
+ return downloadStatus;
+ }
+}
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
new file mode 100644
index 000000000..cf5a84eea
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
@@ -0,0 +1,111 @@
+package de.danoeh.antennapod.core.service.download.handler;
+
+import android.content.Context;
+import android.media.MediaMetadataRetriever;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+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.ChapterUtils;
+import de.danoeh.antennapod.core.util.DownloadError;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Handles a completed media download.
+ */
+public class MediaDownloadedHandler implements Runnable {
+ private static final String TAG = "MediaDownloadedHandler";
+ private final DownloadRequest request;
+ private final DownloadStatus status;
+ private final Context context;
+ private DownloadStatus updatedStatus;
+
+ public MediaDownloadedHandler(@NonNull Context context, @NonNull DownloadStatus status,
+ @NonNull DownloadRequest request) {
+ this.status = status;
+ this.request = request;
+ this.context = context;
+ }
+
+ @Override
+ public void run() {
+ updatedStatus = status;
+ FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
+ if (media == null) {
+ Log.e(TAG, "Could not find downloaded media object in database");
+ return;
+ }
+ media.setDownloaded(true);
+ media.setFile_url(request.getDestination());
+ media.checkEmbeddedPicture(); // enforce check
+
+ // check if file has chapters
+ if (media.getItem() != null && !media.getItem().hasChapters()) {
+ ChapterUtils.loadChaptersFromFileUrl(media);
+ }
+
+ // Get duration
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ String durationStr = null;
+ try {
+ mmr.setDataSource(media.getFile_url());
+ durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ media.setDuration(Integer.parseInt(durationStr));
+ Log.d(TAG, "Duration of file is " + media.getDuration());
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "Invalid file duration: " + durationStr);
+ } catch (Exception e) {
+ Log.e(TAG, "Get duration failed", e);
+ } finally {
+ mmr.release();
+ }
+
+ final FeedItem item = media.getItem();
+
+ try {
+ DBWriter.setFeedMedia(media).get();
+
+ // we've received the media, we don't want to autodownload it again
+ if (item != null) {
+ item.setAutoDownload(false);
+ // setFeedItem() signals (via EventBus) that the item has been updated,
+ // so we do it after the enclosing media has been updated above,
+ // to ensure subscribers will get the updated FeedMedia as well
+ DBWriter.setFeedItem(item).get();
+ }
+
+ if (item != null && UserPreferences.enqueueDownloadedEpisodes()
+ && !DBTasks.isInQueue(context, item.getId())) {
+ DBWriter.addQueueItem(context, item).get();
+ }
+ } catch (InterruptedException e) {
+ 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());
+ }
+
+
+ if (GpodnetPreferences.loggedIn() && item != null) {
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DOWNLOAD)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
+ }
+
+ public DownloadStatus 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
new file mode 100644
index 000000000..5d2c48679
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/PostDownloaderTask.java
@@ -0,0 +1,29 @@
+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/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index d5245cc10..8b87d7c54 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
@@ -515,13 +515,24 @@ public final class DBReader {
* newest events first.
*/
public static List<DownloadStatus> getFeedDownloadLog(Feed feed) {
- Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feed + "]");
+ return getFeedDownloadLog(feed.getId());
+ }
+
+ /**
+ * Loads the download log for a particular feed from the database.
+ *
+ * @param feedId Feed id for which the download log is loaded
+ * @return A list with DownloadStatus objects that represent the feed's download log,
+ * newest events first.
+ */
+ public static List<DownloadStatus> getFeedDownloadLog(long feedId) {
+ Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feedId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = null;
try {
- cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId());
+ cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feedId);
List<DownloadStatus> downloadLog = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
downloadLog.add(DownloadStatus.fromCursor(cursor));
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 84f57e87a..9d37a5f2a 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
@@ -299,7 +299,7 @@ public final class DBTasks {
media.setDownloaded(false);
media.setFile_url(null);
DBWriter.setFeedMedia(media);
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(media.getItem().getFeed()));
}
/**
@@ -558,13 +558,13 @@ public final class DBTasks {
adapter.close();
try {
- DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get();
- DBWriter.setCompleteFeed(updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get();
+ DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[0])).get();
+ DBWriter.setCompleteFeed(updatedFeedsList.toArray(new Feed[0])).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(updatedFeedsList));
return resultFeeds;
}
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 919123950..8f0626c5c 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
@@ -173,7 +173,7 @@ public class DBWriter {
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
}
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(feed));
// we assume we also removed download log entries for the feed or its media files.
// especially important if download or refresh failed, as the user should not be able
@@ -803,7 +803,7 @@ public class DBWriter {
adapter.open();
adapter.setFeedPreferences(preferences);
adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(preferences.getFeedID()));
});
}
@@ -842,7 +842,7 @@ public class DBWriter {
adapter.open();
adapter.setFeedCustomTitle(feed.getId(), feed.getCustomTitle());
adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent());
+ EventBus.getDefault().post(new FeedListUpdateEvent(feed));
});
}