Implemented download queueing

This commit is contained in:
daniel oeh 2012-08-16 18:33:16 +02:00
parent 2f3ddfc35f
commit 1b6a35c8a0
7 changed files with 128 additions and 114 deletions

View File

@ -142,8 +142,7 @@ public class DownloadActivity extends SherlockListActivity implements
boolean handled = false;
switch (item.getItemId()) {
case R.id.cancel_download_item:
requester.cancelDownload(this, selectedDownload.getFeedFile()
.getDownloadId());
requester.cancelDownload(this, selectedDownload.getFeedFile());
handled = true;
break;
}

View File

@ -5,6 +5,7 @@ import java.util.List;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.R;
import android.widget.ArrayAdapter;
@ -121,7 +122,8 @@ public class FeedItemlistAdapter extends ArrayAdapter<FeedItem> {
holder.downloaded.setVisibility(View.GONE);
}
if (item.getMedia().isDownloading()) {
if (DownloadRequester.getInstance().isDownloadingFile(
item.getMedia())) {
holder.downloading.setVisibility(View.VISIBLE);
} else {
holder.downloading.setVisibility(View.GONE);

View File

@ -1,10 +1,9 @@
package de.danoeh.antennapod.feed;
/** Represents a component of a Feed that has to be downloaded*/
/** Represents a component of a Feed that has to be downloaded */
public abstract class FeedFile extends FeedComponent {
protected String file_url;
protected String download_url;
protected long downloadId; // temporary id given by the Android DownloadManager
protected boolean downloaded;
public FeedFile(String file_url, String download_url, boolean downloaded) {
@ -21,24 +20,19 @@ public abstract class FeedFile extends FeedComponent {
public String getFile_url() {
return file_url;
}
public void setFile_url(String file_url) {
this.file_url = file_url;
}
public String getDownload_url() {
return download_url;
}
public void setDownload_url(String download_url) {
this.download_url = download_url;
}
public long getDownloadId() {
return downloadId;
}
public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}
public boolean isDownloaded() {
return downloaded;
}
@ -46,10 +40,4 @@ public abstract class FeedFile extends FeedComponent {
public void setDownloaded(boolean downloaded) {
this.downloaded = downloaded;
}
public boolean isDownloading() {
return downloadId != 0;
}
}

View File

@ -167,8 +167,7 @@ public class FeedManager {
imageFile.delete();
}
} else if (requester.isDownloadingFile(feed.getImage())) {
requester.cancelDownload(context, feed.getImage()
.getDownloadId());
requester.cancelDownload(context, feed.getImage());
}
// delete stored media files and mark them as read
for (FeedItem item : feed.getItems()) {
@ -184,8 +183,7 @@ public class FeedManager {
mediaFile.delete();
} else if (item.getMedia() != null
&& requester.isDownloadingFile(item.getMedia())) {
requester.cancelDownload(context, item.getMedia()
.getDownloadId());
requester.cancelDownload(context, item.getMedia());
}
}

View File

@ -51,6 +51,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.URLUtil;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Debug;
@ -71,6 +72,7 @@ public class DownloadService extends Service {
* queryDownloads()
*/
public static final String ACTION_NOTIFY_DOWNLOADS_CHANGED = "action.de.danoeh.antennapod.service.notifyDownloadsChanged";
public static final String ACTION_ENQUEUE_DOWNLOAD = "action.de.danoeh.antennapod.service.enqueueDownload";
public static final String ACTION_DOWNLOAD_HANDLED = "action.de.danoeh.antennapod.service.download_handled";
/** True if handled feed has an image. */
@ -78,6 +80,9 @@ public class DownloadService extends Service {
public static final String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.service.download_id";
public static final String EXTRA_IMAGE_DOWNLOAD_ID = "extra.de.danoeh.antennapod.service.image_download_id";
/** Extra for ACTION_ENQUEUE_DOWNLOAD intent. */
public static final String EXTRA_REQUEST = "request";
// Download types for ACTION_DOWNLOAD_HANDLED
public static final String EXTRA_DOWNLOAD_TYPE = "extra.de.danoeh.antennapod.service.downloadType";
public static final int DOWNLOAD_TYPE_FEED = 1;
@ -87,6 +92,8 @@ public class DownloadService extends Service {
private ArrayList<DownloadStatus> completedDownloads;
private ExecutorService syncExecutor;
private ExecutorService downloadExecutor;
private DownloadRequester requester;
private FeedManager manager;
private NotificationCompat.Builder notificationBuilder;
@ -98,7 +105,7 @@ public class DownloadService extends Service {
private DownloadObserver downloadObserver;
private List<DownloadStatus> downloads;
private List<Downloader> downloads;
private volatile boolean shutdownInitiated = false;
/** True if service is running. */
@ -117,10 +124,6 @@ public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (waiter != null) {
waiter.interrupt();
}
queryDownloads();
return super.onStartCommand(intent, flags, startId);
}
@ -131,10 +134,12 @@ public class DownloadService extends Service {
Log.d(TAG, "Service started");
isRunning = true;
completedDownloads = new ArrayList<DownloadStatus>();
downloads = new ArrayList<DownloadStatus>();
downloads = new ArrayList<Downloader>();
registerReceiver(downloadReceiver, createIntentFilter());
registerReceiver(onDownloadsChanged, new IntentFilter(
ACTION_NOTIFY_DOWNLOADS_CHANGED));
registerReceiver(downloadQueued, new IntentFilter(
ACTION_ENQUEUE_DOWNLOAD));
syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
@ -153,6 +158,15 @@ public class DownloadService extends Service {
return t;
}
});
downloadExecutor = Executors.newFixedThreadPool(2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
});
manager = FeedManager.getInstance();
requester = DownloadRequester.getInstance();
mediaplayer = new MediaPlayer();
@ -179,6 +193,7 @@ public class DownloadService extends Service {
mediaplayer.release();
unregisterReceiver(downloadReceiver);
unregisterReceiver(onDownloadsChanged);
unregisterReceiver(downloadQueued);
downloadObserver.cancel(true);
createReport();
}
@ -248,6 +263,47 @@ public class DownloadService extends Service {
}
};
private BroadcastReceiver downloadQueued = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_ENQUEUE_DOWNLOAD)) {
if (AppConfig.DEBUG) Log.d(TAG, "Received enqueue request");
Request request = intent.getParcelableExtra(EXTRA_REQUEST);
if (request == null) {
throw new IllegalArgumentException(
"ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
}
DownloadRequester requester = DownloadRequester.getInstance();
FeedFile feedfile = requester.getDownload(request.source);
if (feedfile != null) {
if (waiter != null) {
waiter.interrupt();
}
DownloadStatus status = new DownloadStatus(feedfile);
Downloader downloader = getDownloader(status);
if (downloader != null) {
downloads.add(downloader);
downloadExecutor.submit(downloader);
}
} else {
Log.e(TAG,
"Could not find feedfile in download requester when trying to enqueue new download");
}
}
}
};
private Downloader getDownloader(DownloadStatus status) {
if (URLUtil.isHttpUrl(status.getFeedFile().getDownload_url())) {
return new HttpDownloader(this, status);
}
Log.e(TAG, "Could not find appropriate downloader for "
+ status.getFeedFile().getDownload_url());
return null;
}
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
@SuppressLint("NewApi")
@Override
@ -703,9 +759,15 @@ public class DownloadService extends Service {
}
};
}
public String getDestination() {
return destination;
}
public String getSource() {
return source;
}
}
public DownloadObserver getDownloadObserver() {
return downloadObserver;

View File

@ -1,7 +1,10 @@
package de.danoeh.antennapod.storage;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import android.annotation.SuppressLint;
@ -23,27 +26,24 @@ import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.util.NumberGenerator;
import de.danoeh.antennapod.util.URLChecker;
public class DownloadRequester {// TODO handle externalstorage missing
public class DownloadRequester {
private static final String TAG = "DownloadRequester";
private static final int currentApi = android.os.Build.VERSION.SDK_INT;
public static String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.storage.download_id";
public static String EXTRA_ITEM_ID = "extra.de.danoeh.antennapod.storage.item_id";
public static String ACTION_DOWNLOAD_QUEUED = "action.de.danoeh.antennapod.storage.downloadQueued";
private static boolean STORE_ON_SD = true;
public static String IMAGE_DOWNLOADPATH = "images/";
public static String FEED_DOWNLOADPATH = "cache/";
public static String MEDIA_DOWNLOADPATH = "media/";
private static DownloadRequester downloader;
private DownloadManager manager;
private List<FeedFile> downloads;
Map<String, FeedFile> downloads;
private DownloadRequester() {
downloads = new CopyOnWriteArrayList<FeedFile>();
downloads = new ConcurrentHashMap<String, FeedFile>();
}
public static DownloadRequester getInstance() {
@ -53,8 +53,7 @@ public class DownloadRequester {// TODO handle externalstorage missing
return downloader;
}
@SuppressLint("NewApi")
private long download(Context context, FeedFile item, File dest) {
private void download(Context context, FeedFile item, File dest) {
if (!isDownloadingFile(item)) {
if (dest.exists()) {
if (AppConfig.DEBUG)
@ -64,74 +63,58 @@ public class DownloadRequester {// TODO handle externalstorage missing
if (AppConfig.DEBUG)
Log.d(TAG,
"Requesting download of url " + item.getDownload_url());
downloads.add(item);
downloads.put(item.getDownload_url(), item);
item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(item.getDownload_url())).setDestinationUri(Uri
.fromFile(dest));
if (AppConfig.DEBUG)
Log.d(TAG, "Version is " + currentApi);
if (currentApi >= 11) {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
} else {
request.setVisibleInDownloadsUi(false);
request.setShowRunningNotification(false);
}
// TODO Set Allowed Network Types
DownloadManager manager = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = manager.enqueue(request);
item.setDownloadId(downloadId);
item.setFile_url(dest.toString());
context.startService(new Intent(context, DownloadService.class));
DownloadService.Request request = new DownloadService.Request(
item.getFile_url(), item.getDownload_url());
Intent queueIntent = new Intent(
DownloadService.ACTION_ENQUEUE_DOWNLOAD);
queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
if (!DownloadService.isRunning) {
context.startService(new Intent(context, DownloadService.class));
}
context.sendBroadcast(queueIntent);
context.sendBroadcast(new Intent(ACTION_DOWNLOAD_QUEUED));
return downloadId;
} else {
Log.e(TAG, "URL " + item.getDownload_url()
+ " is already being downloaded");
return 0;
}
}
public long downloadFeed(Context context, Feed feed) {
return download(context, feed, new File(getFeedfilePath(context),
public void downloadFeed(Context context, Feed feed) {
download(context, feed, new File(getFeedfilePath(context),
getFeedfileName(feed)));
}
public long downloadImage(Context context, FeedImage image) {
return download(context, image, new File(getImagefilePath(context),
public void downloadImage(Context context, FeedImage image) {
download(context, image, new File(getImagefilePath(context),
getImagefileName(image)));
}
public long downloadMedia(Context context, FeedMedia feedmedia) {
return download(context, feedmedia,
public void downloadMedia(Context context, FeedMedia feedmedia) {
download(context, feedmedia,
new File(getMediafilePath(context, feedmedia),
getMediafilename(feedmedia)));
}
/**
* Cancels a running download.
*
* @param context
* A context needed to get the DownloadManager service
* @param id
* ID of the download to cancel
* */
public void cancelDownload(final Context context, final long id) {
public void cancelDownload(final Context context, final FeedFile f) {
cancelDownload(context, f.getDownload_url());
}
/**
* Cancels a running download.
* */
public void cancelDownload(final Context context, final String download_url) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling download with id " + id);
DownloadManager dm = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE);
int removed = dm.remove(id);
if (removed > 0) {
FeedFile f = getFeedFile(id);
if (f != null) {
downloads.remove(f);
f.setFile_url(null);
f.setDownloadId(0);
}
Log.d(TAG, "Cancelling download with url " + download_url);
FeedFile download = downloads.remove(download_url);
if (download != null) {
download.setFile_url(null);
notifyDownloadService(context);
}
}
@ -142,28 +125,17 @@ public class DownloadRequester {// TODO handle externalstorage missing
Log.d(TAG, "Cancelling all running downloads");
DownloadManager dm = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE);
for (FeedFile f : downloads) {
for (FeedFile f : downloads.values()) {
dm.remove(f.getDownloadId());
f.setFile_url(null);
f.setDownloadId(0);
}
downloads.clear();
notifyDownloadService(context);
}
/** Get a feedfile by its download id */
public FeedFile getFeedFile(long id) {
for (FeedFile f : downloads) {
if (f.getDownloadId() == id) {
return f;
}
}
return null;
}
/** Returns true if there is at least one Feed in the downloads queue. */
public boolean isDownloadingFeeds() {
for (FeedFile f : downloads) {
for (FeedFile f : downloads.values()) {
if (f.getClass() == Feed.class) {
return true;
}
@ -173,22 +145,19 @@ public class DownloadRequester {// TODO handle externalstorage missing
/** Checks if feedfile is in the downloads list */
public boolean isDownloadingFile(FeedFile item) {
for (FeedFile f : downloads) {
if (f.getDownload_url().equals(item.getDownload_url())) {
return true;
}
if (item.getDownload_url() != null) {
return downloads.containsKey(item.getDownload_url());
}
return false;
}
public FeedFile getDownload(String downloadUrl) {
return downloads.get(downloadUrl);
}
/** Checks if feedfile with the given download url is in the downloads list */
public boolean isDownloadingFile(String downloadUrl) {
for (FeedFile f : downloads) {
if (f.getDownload_url().equals(downloadUrl)) {
return true;
}
}
return false;
return downloads.get(downloadUrl) != null;
}
public boolean hasNoDownloads() {
@ -204,10 +173,6 @@ public class DownloadRequester {// TODO handle externalstorage missing
downloads.remove(f);
}
public List<FeedFile> getDownloads() {
return downloads;
}
/** Get the number of uncompleted Downloads */
public int getNumberOfDownloads() {
return downloads.size();
@ -243,6 +208,7 @@ public class DownloadRequester {// TODO handle externalstorage missing
/** Notifies the DownloadService to check if there are any Downloads left */
public void notifyDownloadService(Context context) {
context.sendBroadcast(new Intent(DownloadService.ACTION_NOTIFY_DOWNLOADS_CHANGED));
context.sendBroadcast(new Intent(
DownloadService.ACTION_NOTIFY_DOWNLOADS_CHANGED));
}
}

View File

@ -81,8 +81,7 @@ public class FeedItemMenuHandler {
manager.deleteFeedMedia(context, selectedItem.getMedia());
break;
case R.id.cancel_download_item:
requester.cancelDownload(context, selectedItem.getMedia()
.getDownloadId());
requester.cancelDownload(context, selectedItem.getMedia());
break;
case R.id.mark_read_item:
manager.markItemRead(context, selectedItem, true);