Small touches on DownloadService.java

This commit is contained in:
Danial Klimkin 2017-04-09 22:03:09 +02:00
parent 4d256b7323
commit b34910261c
2 changed files with 175 additions and 138 deletions

View File

@ -0,0 +1,35 @@
package de.danoeh.antennapod.core.tests.util.service.download;
import android.test.AndroidTestCase;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.service.download.DownloadService;
public class DownloadServiceTest extends AndroidTestCase {
public void testRemoveDuplicateImages() {
List<FeedItem> items = new ArrayList<>();
for (int i = 0; i < 50; i++) {
FeedItem item = new FeedItem();
String url = (i % 5 == 0) ? "dupe_url" : String.format("url_%d", i);
item.setImage(new FeedImage(null, url, ""));
items.add(item);
}
Feed feed = new Feed();
feed.setItems(items);
DownloadService.removeDuplicateImages(feed);
assertEquals(50, items.size());
for (int i = 0; i < items.size(); i++) {
FeedItem item = items.get(i);
String want = (i == 0) ? "dupe_url" : (i % 5 == 0) ? null : String.format("url_%d", i);
assertEquals(want, item.getImageLocation());
}
}
}

View File

@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.service.download;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@ -15,13 +14,15 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.webkit.URLUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;
import java.io.File;
@ -30,8 +31,10 @@ import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
@ -231,9 +234,10 @@ public class DownloadService extends Service {
queryDownloadsAsync();
}
} catch (InterruptedException e) {
Log.d(TAG, "DownloadCompletionThread was interrupted");
Log.e(TAG, "DownloadCompletionThread was interrupted");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
e.printStackTrace();
Log.e(TAG, "ExecutionException in DownloadCompletionThread: " + e.getMessage());
numberOfDownloads.decrementAndGet();
}
}
@ -251,7 +255,6 @@ public class DownloadService extends Service {
return Service.START_NOT_STICKY;
}
@SuppressLint("NewApi")
@Override
public void onCreate() {
Log.d(TAG, "Service started");
@ -337,8 +340,7 @@ public class DownloadService extends Service {
}
private void setupNotificationBuilders() {
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.stat_notify_sync);
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.stat_notify_sync);
notificationCompatBuilder = new NotificationCompat.Builder(this)
.setOngoing(true)
@ -347,7 +349,6 @@ public class DownloadService extends Service {
.setSmallIcon(R.drawable.stat_notify_sync)
.setVisibility(Notification.VISIBILITY_PUBLIC);
Log.d(TAG, "Notification set up");
}
@ -356,50 +357,22 @@ public class DownloadService extends Service {
* before setupNotificationBuilders.
*/
private Notification updateNotifications() {
if (notificationCompatBuilder == null) {
return null;
}
String contentTitle = getString(R.string.download_notification_title);
int numDownloads = requester.getNumberOfDownloads();
String downloadsLeft;
if (numDownloads > 0) {
downloadsLeft = getResources()
.getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads);
} else {
downloadsLeft = getString(R.string.downloads_processing);
}
if (notificationCompatBuilder != null) {
String downloadsLeft = (numDownloads > 0) ?
getResources().getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads) :
getString(R.string.downloads_processing);
String bigText = compileNotificationString(downloads);
StringBuilder bigText = new StringBuilder("");
for (int i = 0; i < downloads.size(); i++) {
Downloader downloader = downloads.get(i);
final DownloadRequest request = downloader
.getDownloadRequest();
if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
if (request.getTitle() != null) {
if (i > 0) {
bigText.append("\n");
}
bigText.append("\u2022 ").append(request.getTitle());
}
} else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
if (request.getTitle() != null) {
if (i > 0) {
bigText.append("\n");
}
bigText.append("\u2022 ").append(request.getTitle())
.append(" (").append(request.getProgressPercent())
.append("%)");
}
}
}
notificationCompatBuilder.setContentTitle(contentTitle);
notificationCompatBuilder.setContentText(downloadsLeft);
if (bigText != null) {
notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText.toString()));
}
notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
return notificationCompatBuilder.build();
}
return null;
}
private Downloader getDownloader(String downloadUrl) {
for (Downloader downloader : downloads) {
@ -467,16 +440,12 @@ public class DownloadService extends Service {
}
private Downloader getDownloader(DownloadRequest request) {
if (URLUtil.isHttpUrl(request.getSource())
|| URLUtil.isHttpsUrl(request.getSource())) {
return new HttpDownloader(request);
}
Log.e(TAG,
"Could not find appropriate downloader for "
+ request.getSource()
);
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);
}
/**
* Remove download from the DownloadRequester list and from the
@ -484,8 +453,7 @@ public class DownloadService extends Service {
*/
private void removeDownload(final Downloader d) {
handler.post(() -> {
Log.d(TAG, "Removing downloader: "
+ d.getDownloadRequest().getSource());
Log.d(TAG, "Removing downloader: " + d.getDownloadRequest().getSource());
boolean rc = downloads.remove(d);
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
@ -532,10 +500,8 @@ public class DownloadService extends Service {
Log.d(TAG, "Creating report");
// create notification object
Notification notification = new NotificationCompat.Builder(this)
.setTicker(
getString(R.string.download_report_title))
.setContentTitle(
getString(R.string.download_report_content_title))
.setTicker(getString(R.string.download_report_title))
.setContentTitle(getString(R.string.download_report_content_title))
.setContentText(
String.format(
getString(R.string.download_report_content),
@ -585,8 +551,8 @@ public class DownloadService extends Service {
private void postAuthenticationNotification(final DownloadRequest downloadRequest) {
handler.post(() -> {
final String resourceTitle = (downloadRequest.getTitle() != null)
? downloadRequest.getTitle() : downloadRequest.getSource();
final String resourceTitle = (downloadRequest.getTitle() != null) ?
downloadRequest.getTitle() : downloadRequest.getSource();
NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
builder.setTicker(getText(R.string.authentication_notification_title))
@ -611,7 +577,6 @@ public class DownloadService extends Service {
private void handleCompletedFeedDownload(DownloadRequest request) {
Log.d(TAG, "Handling completed Feed Download");
feedSyncThread.submitCompletedDownload(request);
}
/**
@ -660,6 +625,8 @@ public class DownloadService extends Service {
parserService.submit(new FeedParserTask(request));
tasks++;
} catch (InterruptedException e) {
Log.e(TAG, "FeedSyncThread was interrupted");
Thread.currentThread().interrupt();
return null;
}
@ -678,6 +645,7 @@ public class DownloadService extends Service {
} catch (InterruptedException e) {
Log.d(TAG, "interrupted while waiting for more downloads");
tasks += pollCompletedDownloads();
Thread.currentThread().interrupt();
} finally {
currentTime = System.currentTimeMillis();
}
@ -695,9 +663,11 @@ public class DownloadService extends Service {
if (result != null) {
results.add(result);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Log.e(TAG, "FeedSyncThread was interrupted");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage());
}
}
@ -706,7 +676,7 @@ public class DownloadService extends Service {
private int pollCompletedDownloads() {
int tasks = 0;
for (int i = 0; i < completedRequests.size(); i++) {
while (!completedRequests.isEmpty()) {
parserService.submit(new FeedParserTask(completedRequests.poll()));
tasks++;
}
@ -732,8 +702,11 @@ public class DownloadService extends Service {
if (dbUpdateFuture != null) {
try {
dbUpdateFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Log.e(TAG, "FeedSyncThread was interrupted");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage());
}
}
@ -770,14 +743,14 @@ public class DownloadService extends Service {
try {
dbUpdateFuture.get();
} catch (InterruptedException e) {
Log.e(TAG, "interrupted while updating the db");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
e.printStackTrace();
Log.e(TAG, "ExecutionException while updating the db: " + e.getMessage());
}
}
Log.d(TAG, "Shutting down");
}
/**
@ -854,16 +827,16 @@ public class DownloadService extends Service {
// 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));
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));
saveDownloadStatus(
new DownloadStatus(feed, feed.getHumanReadableIdentifier(), reason,
successful, reasonDetailed));
return null;
}
}
@ -884,26 +857,6 @@ public class DownloadService extends Service {
return true;
}
/**
* Checks if the FeedItems of this feed have images that point
* to the same URL. If two FeedItems have an image that points to
* the same URL, the reference of the second item is removed, so that every image
* reference is unique.
*/
private void removeDuplicateImages(Feed feed) {
for (int x = 0; x < feed.getItems().size(); x++) {
for (int y = x + 1; y < feed.getItems().size(); y++) {
FeedItem item1 = feed.getItems().get(x);
FeedItem item2 = feed.getItems().get(y);
if (item1.hasItemImage() && item2.hasItemImage()) {
if (TextUtils.equals(item1.getImage().getDownload_url(), item2.getImage().getDownload_url())) {
item2.setImage(null);
}
}
}
}
}
private boolean hasValidFeedItems(Feed feed) {
for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) {
@ -911,8 +864,7 @@ public class DownloadService extends Service {
return false;
}
if (item.getPubDate() == null) {
Log.e(TAG,
"Item has no pubDate. Using current time as pubDate");
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());
}
@ -986,8 +938,11 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
try {
DBWriter.setFeedMedia(media).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Log.e(TAG, "FailedDownloadHandler was interrupted");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
Log.e(TAG, "ExecutionException in FailedDownloadHandler: " + e.getMessage());
}
}
}
@ -1023,17 +978,14 @@ public class DownloadService extends Service {
ChapterUtils.loadChaptersFromFileUrl(media);
// Get duration
MediaMetadataRetriever mmr = null;
try {
mmr = new MediaMetadataRetriever();
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(media.getFile_url());
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
try {
media.setDuration(Integer.parseInt(durationStr));
Log.d(TAG, "Duration of file is " + media.getDuration());
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
Log.d(TAG, "Invalid file duration: " + durationStr);
} finally {
if (mmr != null) {
mmr.release();
@ -1055,8 +1007,11 @@ public class DownloadService extends Service {
!DBTasks.isInQueue(DownloadService.this, item.getId())) {
DBWriter.addQueueItem(DownloadService.this, item).get();
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Log.e(TAG, "MediaHandlerThread was interrupted");
Thread.currentThread().interrupt();
} 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());
}
@ -1107,6 +1062,7 @@ public class DownloadService extends Service {
}
});
}
}
@ -1130,4 +1086,50 @@ public class DownloadService extends Service {
}
}
/**
* Checks if the FeedItems of this feed have images that point to the same URL. If two FeedItems
* have an image that points to the same URL, the reference of the second item is removed, so
* that every image reference is unique.
*/
@VisibleForTesting
public static void removeDuplicateImages(Feed feed) {
Set<String> known = new HashSet<String>();
for (FeedItem item : feed.getItems()) {
String url = item.hasItemImage() ? item.getImage().getDownload_url() : null;
if (url != null) {
if (known.contains(url)) {
item.setImage(null);
} else {
known.add(url);
}
}
}
}
private static String compileNotificationString(List<Downloader> downloads) {
List<String> lines = new ArrayList<>(downloads.size());
for (Downloader downloader : downloads) {
StringBuilder line = new StringBuilder("\u2022 ");
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 StringUtils.join(lines, '\n');
}
}