diff options
Diffstat (limited to 'core')
11 files changed, 300 insertions, 377 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java deleted file mode 100644 index 2ce506c22..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java +++ /dev/null @@ -1,191 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.app.Activity; -import android.content.*; -import android.os.Handler; -import android.os.IBinder; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.service.download.Downloader; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Provides access to the DownloadService's list of items that are currently being downloaded. - * The DownloadObserver object should be created in the activity's onCreate() method. resume() and pause() - * should be called in the activity's onResume() and onPause() methods - */ -public class DownloadObserver { - private static final String TAG = "DownloadObserver"; - - /** - * Time period between update notifications. - */ - public static final int WAITING_INTERVAL_MS = 3000; - - private volatile Activity activity; - private final Handler handler; - private final Callback callback; - - private DownloadService downloadService = null; - private AtomicBoolean mIsBound = new AtomicBoolean(false); - - private Thread refresherThread; - private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false); - - private AtomicInteger users = new AtomicInteger(0); - - - /** - * Creates a new download observer. - * - * @param activity Used for registering receivers - * @param handler All callback methods are executed on this handler. The handler MUST run on the GUI thread. - * @param callback Callback methods for posting content updates - * @throws java.lang.IllegalArgumentException if one of the arguments is null. - */ - public DownloadObserver(Activity activity, Handler handler, Callback callback) { - Validate.notNull(activity); - Validate.notNull(handler); - Validate.notNull(callback); - - this.activity = activity; - this.handler = handler; - this.callback = callback; - } - - public void onResume() { - Log.d(TAG, "DownloadObserver resumed"); - if(users.getAndIncrement() == 0) { - activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED)); - connectToDownloadService(); - } - } - - public void onPause() { - Log.d(TAG, "DownloadObserver paused"); - if(users.decrementAndGet() > 0) { - return; - } - try { - activity.unregisterReceiver(contentChangedReceiver); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - try { - activity.unbindService(mConnection); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - stopRefresher(); - } - - private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // reconnect to DownloadService if connection has been closed - if (downloadService == null) { - connectToDownloadService(); - } - if (downloadService != null) { - callback.onContentChanged(downloadService.getDownloads()); - } else { - // the service is gone, there are no more downloads. - callback.onContentChanged(new ArrayList<Downloader>()); - } - startRefresher(); - } - }; - - public interface Callback { - void onContentChanged(List<Downloader> downloaderList); - } - - private void connectToDownloadService() { - activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0); - } - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceDisconnected(ComponentName className) { - downloadService = null; - mIsBound.set(false); - stopRefresher(); - Log.i(TAG, "Closed connection with DownloadService."); - } - - public void onServiceConnected(ComponentName name, IBinder service) { - downloadService = ((DownloadService.LocalBinder) service) - .getService(); - mIsBound.set(true); - if (BuildConfig.DEBUG) - Log.d(TAG, "Connection to service established"); - List<Downloader> downloaderList = downloadService.getDownloads(); - if (downloaderList != null && !downloaderList.isEmpty()) { - callback.onContentChanged(downloaderList); - startRefresher(); - } - } - }; - - private void stopRefresher() { - if (refresherThread != null) { - refresherThread.interrupt(); - } - } - - private void startRefresher() { - if (refresherThread == null || refresherThread.isInterrupted()) { - refresherThread = new Thread(new RefresherThread()); - refresherThread.start(); - } - } - - private class RefresherThread implements Runnable { - - public void run() { - refresherThreadRunning.set(true); - while (!Thread.interrupted()) { - try { - Thread.sleep(WAITING_INTERVAL_MS); - } catch (InterruptedException e) { - Log.d(TAG, "Refresher thread was interrupted"); - } - if (mIsBound.get()) { - postUpdate(); - } - } - refresherThreadRunning.set(false); - } - - private void postUpdate() { - handler.post(new Runnable() { - @Override - public void run() { - if (downloadService != null) { - List<Downloader> downloaderList = downloadService.getDownloads(); - callback.onContentChanged(downloaderList); - if (downloaderList == null || downloaderList.isEmpty()) { - Thread.currentThread().interrupt(); - } - } else { - callback.onContentChanged(new ArrayList<Downloader>()); - } - } - }); - } - } - - public void setActivity(Activity activity) { - Validate.notNull(activity); - this.activity = activity; - } - -} - diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java new file mode 100644 index 000000000..124fd3e64 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java @@ -0,0 +1,28 @@ +package de.danoeh.antennapod.core.event; + +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.core.service.download.Downloader; + +public class DownloadEvent { + + public final DownloaderUpdate update; + + private DownloadEvent(DownloaderUpdate downloader) { + this.update = downloader; + } + + public static DownloadEvent refresh(List<Downloader> list) { + list = new ArrayList<>(list); + DownloaderUpdate update = new DownloaderUpdate(list); + return new DownloadEvent(update); + } + + @Override + public String toString() { + return "DownloadEvent{" + + "update=" + update + + '}'; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java new file mode 100644 index 000000000..dcb033267 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java @@ -0,0 +1,53 @@ +package de.danoeh.antennapod.core.event; + +import java.util.Arrays; +import java.util.List; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.service.download.Downloader; +import de.danoeh.antennapod.core.util.LongList; + +public class DownloaderUpdate { + + /* Downloaders that are currently running */ + public final List<Downloader> downloaders; + + /** + * IDs of feeds that are currently being downloaded + * Often used to show some progress wheel in the action bar + */ + public final long[] feedIds; + + /** + * IDs of feed media that are currently being downloaded + * Can be used to show and update download progress bars + */ + public final long[] mediaIds; + + public DownloaderUpdate(List<Downloader> downloaders) { + this.downloaders = downloaders; + LongList feedIds1 = new LongList(), mediaIds1 = new LongList(); + for(Downloader d1 : downloaders) { + int type = d1.getDownloadRequest().getFeedfileType(); + long id = d1.getDownloadRequest().getFeedfileId(); + if(type == Feed.FEEDFILETYPE_FEED) { + feedIds1.add(id); + } else if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + mediaIds1.add(id); + } + } + + this.feedIds = feedIds1.toArray(); + this.mediaIds = mediaIds1.toArray(); + } + + @Override + public String toString() { + return "DownloaderUpdate{" + + "downloaders=" + downloaders + + ", feedIds=" + Arrays.toString(feedIds) + + ", mediaIds=" + Arrays.toString(mediaIds) + + '}'; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java index a4a79187e..7ff241456 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java @@ -6,6 +6,7 @@ import android.support.annotation.NonNull; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import java.util.Arrays; import java.util.List; import de.danoeh.antennapod.core.feed.FeedItem; @@ -32,6 +33,10 @@ public class FeedItemEvent { return new FeedItemEvent(Action.UPDATE, items); } + public static FeedItemEvent updated(FeedItem... items) { + return new FeedItemEvent(Action.UPDATE, Arrays.asList(items)); + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java index 2667a2e12..3425e8a8e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java @@ -26,7 +26,6 @@ public class EventDistributor extends Observable { public static final int UNREAD_ITEMS_UPDATE = 2; public static final int DOWNLOADLOG_UPDATE = 8; public static final int PLAYBACK_HISTORY_UPDATE = 16; - public static final int DOWNLOAD_QUEUED = 32; public static final int DOWNLOAD_HANDLED = 64; public static final int PLAYER_STATUS_UPDATE = 128; @@ -88,10 +87,6 @@ public class EventDistributor extends Observable { Validate.isInstanceOf(EventListener.class, observer); } - public void sendDownloadQueuedBroadcast() { - addEvent(DOWNLOAD_QUEUED); - } - public void sendUnreadItemsUpdateBroadcast() { addEvent(UNREAD_ITEMS_UPDATE); } @@ -108,10 +103,6 @@ public class EventDistributor extends Observable { addEvent(DOWNLOADLOG_UPDATE); } - public void sendDownloadHandledBroadcast() { - addEvent(DOWNLOAD_HANDLED); - } - public void sendPlayerStatusUpdateBroadcast() { addEvent(PLAYER_STATUS_UPDATE); } public static abstract class EventListener implements Observer { 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 0698107a7..677765e92 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 @@ -53,7 +53,8 @@ import javax.xml.parsers.ParserConfigurationException; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.feed.EventDistributor; +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.FeedImage; import de.danoeh.antennapod.core.feed.FeedItem; @@ -74,6 +75,7 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.DownloadError; import de.danoeh.antennapod.core.util.InvalidFeedException; +import de.greenrobot.event.EventBus; /** * Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent. @@ -102,12 +104,6 @@ public class DownloadService extends Service { public static final String EXTRA_DOWNLOAD_URL = "downloadUrl"; /** - * Sent by the DownloadService when the content of the downloads list - * changes. - */ - public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.core.service.downloadsContentChanged"; - - /** * Extra for ACTION_ENQUEUE_DOWNLOAD intent. */ public static final String EXTRA_REQUEST = "request"; @@ -155,6 +151,8 @@ public class DownloadService extends Service { private static final int SCHED_EX_POOL_SIZE = 1; private ScheduledThreadPoolExecutor schedExecutor; + private Handler postHandler = new Handler(); + private final IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder { @@ -180,10 +178,7 @@ public class DownloadService extends Service { final int type = status.getFeedfileType(); if (successful) { if (type == Feed.FEEDFILETYPE_FEED) { - handleCompletedFeedDownload(downloader - .getDownloadRequest()); - } else if (type == FeedImage.FEEDFILETYPE_FEEDIMAGE) { - handleCompletedImageDownload(status, downloader.getDownloadRequest()); + handleCompletedFeedDownload(downloader.getDownloadRequest()); } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest()); } @@ -202,9 +197,22 @@ public class DownloadService extends Service { Log.e(TAG, "Download failed"); saveDownloadStatus(status); handleFailedDownload(status, downloader.getDownloadRequest()); + + // to make lists reload the failed item, we fake an item update + if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + long id = status.getFeedfileId(); + FeedMedia media = DBReader.getFeedMedia(id); + EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); + } + } + } else { + // if FeedMedia download has been canceled, fake FeedItem update + // so that lists reload that it + if(status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId()); + EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); } } - sendDownloadHandledIntent(); queryDownloadsAsync(); } } catch (InterruptedException e) { @@ -306,6 +314,9 @@ public class DownloadService extends Service { updateReport(); } + postHandler.removeCallbacks(postDownloaderTask); + EventBus.getDefault().postSticky(DownloadEvent.refresh(Collections.emptyList())); + stopForeground(true); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(NOTIFICATION_ID); @@ -407,15 +418,14 @@ public class DownloadService extends Service { } else { Log.e(TAG, "Could not cancel download with url " + url); } - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); + postDownloaders(); } else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) { for (Downloader d : downloads) { d.cancel(); Log.d(TAG, "Cancelled all downloads"); } - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); - + postDownloaders(); } queryDownloads(); } @@ -434,13 +444,14 @@ public class DownloadService extends Service { if (downloader != null) { numberOfDownloads.incrementAndGet(); // smaller rss feeds before bigger media files - if(request.getFeedfileId() == Feed.FEEDFILETYPE_FEED) { + if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { downloads.add(0, downloader); } else { downloads.add(downloader); } downloadExecutor.submit(downloader); - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); + + postDownloaders(); } queryDownloads(); @@ -471,7 +482,7 @@ public class DownloadService extends Service { boolean rc = downloads.remove(d); Log.d(TAG, "Result of downloads.remove: " + rc); DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); + postDownloaders(); } }); } @@ -487,10 +498,6 @@ public class DownloadService extends Service { DBWriter.addDownloadStatus(status); } - private void sendDownloadHandledIntent() { - EventDistributor.getInstance().sendDownloadHandledBroadcast(); - } - /** * 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 @@ -607,14 +614,6 @@ public class DownloadService extends Service { } /** - * Is called whenever a Feed-Image is downloaded - */ - private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) { - Log.d(TAG, "Handling completed Image Download"); - syncExecutor.execute(new ImageHandlerThread(status, request)); - } - - /** * Is called whenever a FeedMedia is downloaded. */ private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) { @@ -767,8 +766,6 @@ public class DownloadService extends Service { numberOfDownloads.decrementAndGet(); } - sendDownloadHandledIntent(); - queryDownloadsAsync(); } }); @@ -1013,40 +1010,6 @@ public class DownloadService extends Service { } /** - * Handles a completed image download. - */ - class ImageHandlerThread implements Runnable { - - private DownloadRequest request; - private DownloadStatus status; - - public ImageHandlerThread(DownloadStatus status, DownloadRequest request) { - Validate.notNull(status); - Validate.notNull(request); - - this.status = status; - this.request = request; - } - - @Override - public void run() { - FeedImage image = DBReader.getFeedImage(request.getFeedfileId()); - if (image == null) { - throw new IllegalStateException("Could not find downloaded image in database"); - } - - image.setFile_url(request.getDestination()); - image.setDownloaded(true); - - saveDownloadStatus(status); - sendDownloadHandledIntent(); - DBWriter.setFeedImage(image); - numberOfDownloads.decrementAndGet(); - queryDownloadsAsync(); - } - } - - /** * Handles a completed media download. */ class MediaHandlerThread implements Runnable { @@ -1064,8 +1027,7 @@ public class DownloadService extends Service { @Override public void run() { - FeedMedia media = DBReader.getFeedMedia( - request.getFeedfileId()); + FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId()); if (media == null) { throw new IllegalStateException( "Could not find downloaded media object in database"); @@ -1121,7 +1083,6 @@ public class DownloadService extends Service { } saveDownloadStatus(status); - sendDownloadHandledIntent(); if(GpodnetPreferences.loggedIn()) { FeedItem item = media.getItem(); @@ -1174,16 +1135,24 @@ public class DownloadService extends Service { } } - public List<Downloader> getDownloads() { - if (downloads == null) { - // this is unusual, but it should be OK, we'll return - // an empty list to make it easy for people - return new ArrayList<Downloader>(); + + private long lastPost = 0; + + final Runnable postDownloaderTask = new Runnable() { + @Override + public void run() { + List<Downloader> list = Collections.unmodifiableList(downloads); + EventBus.getDefault().postSticky(DownloadEvent.refresh(list)); + postHandler.postDelayed(postDownloaderTask, 1500); } + }; - // return a copy of downloads, but the copy doesn't need to be synchronized. - synchronized (downloads) { - return new ArrayList<Downloader>(downloads); + private void postDownloaders() { + long now = System.currentTimeMillis(); + if(now - lastPost >= 250) { + postHandler.removeCallbacks(postDownloaderTask); + postDownloaderTask.run(); + lastPost = now; } } 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 a422a3b0c..c0a30f740 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 @@ -3,13 +3,13 @@ package de.danoeh.antennapod.core.storage; import android.database.Cursor; import android.util.Log; -import org.apache.commons.lang3.StringUtils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; @@ -160,7 +160,7 @@ public final class DBReader { * The method does NOT change the items-attribute of the feed. */ public static List<FeedItem> getFeedItemList(final Feed feed) { - Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); + Log.d(TAG, "getFeedItemList() called with: " + "feed = [" + feed + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -169,11 +169,10 @@ public final class DBReader { List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor); itemlistCursor.close(); + adapter.close(); Collections.sort(items, new FeedItemPubdateComparator()); - adapter.close(); - for (FeedItem item : items) { item.setFeed(feed); } @@ -182,6 +181,7 @@ public final class DBReader { } public static List<FeedItem> extractItemlistFromCursor(Cursor itemlistCursor) { + Log.d(TAG, "extractItemlistFromCursor() called with: " + "itemlistCursor = [" + itemlistCursor + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor); @@ -189,53 +189,61 @@ public final class DBReader { return result; } - private static List<FeedItem> extractItemlistFromCursor( - PodDBAdapter adapter, Cursor itemlistCursor) { - ArrayList<String> itemIds = new ArrayList<>(); - List<FeedItem> items = new ArrayList<>(itemlistCursor.getCount()); + private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter, + Cursor cursor) { + List<FeedItem> result = new ArrayList<>(cursor.getCount()); - if (itemlistCursor.moveToFirst()) { + LongList imageIds = new LongList(cursor.getCount()); + LongList itemIds = new LongList(cursor.getCount()); + if (cursor.moveToFirst()) { do { - int indexImage = itemlistCursor.getColumnIndex(PodDBAdapter.KEY_IMAGE); - long imageId = itemlistCursor.getLong(indexImage); - FeedImage image = null; - if (imageId != 0) { - image = getFeedImage(adapter, imageId); - } + int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE); + long imageId = cursor.getLong(indexImage); + imageIds.add(imageId); - FeedItem item = FeedItem.fromCursor(itemlistCursor); + FeedItem item = FeedItem.fromCursor(cursor); + result.add(item); + itemIds.add(item.getId()); + } while (cursor.moveToNext()); + Map<Long,FeedImage> images = getFeedImages(adapter, imageIds.toArray()); + Map<Long,FeedMedia> medias = getFeedMedia(adapter, itemIds.toArray()); + for(int i=0; i < result.size(); i++) { + FeedItem item = result.get(i); + long imageId = imageIds.get(i); + FeedImage image = images.get(imageId); item.setImage(image); - - itemIds.add(String.valueOf(item.getId())); - - items.add(item); - } while (itemlistCursor.moveToNext()); + FeedMedia media = medias.get(item.getId()); + item.setMedia(media); + if(media != null) { + media.setItem(item); + } + } } - - extractMediafromItemlist(adapter, items, itemIds); - return items; + return result; } - private static void extractMediafromItemlist(PodDBAdapter adapter, - List<FeedItem> items, ArrayList<String> itemIds) { + private static Map<Long,FeedMedia> getFeedMedia(PodDBAdapter adapter, + long... itemIds) { - List<FeedItem> itemsCopy = new ArrayList<>(items); - Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds - .toArray(new String[itemIds.size()])); - if (cursor.moveToFirst()) { - do { - int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM); - long itemId = cursor.getLong(index); - // find matching feed item - FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); - if (item != null) { + ArrayList<String> ids = new ArrayList<>(itemIds.length); + for(long itemId : itemIds) { + ids.add(String.valueOf(itemId)); + } + Map<Long,FeedMedia> result = new HashMap<>(itemIds.length); + Cursor cursor = adapter.getFeedMediaCursor(ids.toArray(new String[0])); + try { + if (cursor.moveToFirst()) { + do { + int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM); + long itemId = cursor.getLong(index); FeedMedia media = FeedMedia.fromCursor(cursor); - item.setMedia(media); - item.getMedia().setItem(item); - } - } while (cursor.moveToNext()); + result.put(itemId, media); + } while (cursor.moveToNext()); + } + } finally { + cursor.close(); } - cursor.close(); + return result; } private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, @@ -261,16 +269,6 @@ public final class DBReader { return feed; } - private static FeedItem getMatchingItemForMedia(long itemId, - List<FeedItem> items) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return item; - } - } - return null; - } - static List<FeedItem> getQueue(PodDBAdapter adapter) { Log.d(TAG, "getQueue()"); Cursor itemlistCursor = adapter.getQueueCursor(); @@ -288,6 +286,7 @@ public final class DBReader { * list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties. */ public static LongList getQueueIDList() { + Log.d(TAG, "getQueueIDList() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); LongList result = getQueueIDList(adapter); @@ -296,7 +295,6 @@ public final class DBReader { } static LongList getQueueIDList(PodDBAdapter adapter) { - adapter.open(); Cursor queueCursor = adapter.getQueueIDCursor(); LongList queueIds = new LongList(queueCursor.getCount()); @@ -317,7 +315,7 @@ public final class DBReader { * list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties. */ public static List<FeedItem> getQueue() { - Log.d(TAG, "getQueue()"); + Log.d(TAG, "getQueue() called with: " + ""); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -332,7 +330,7 @@ public final class DBReader { * @return A list of FeedItems whose episdoe has been downloaded. */ public static List<FeedItem> getDownloadedItems() { - Log.d(TAG, "Extracting downloaded items"); + Log.d(TAG, "getDownloadedItems() called with: " + ""); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -342,9 +340,10 @@ public final class DBReader { itemlistCursor); itemlistCursor.close(); loadAdditionalFeedItemListData(items); + adapter.close(); + Collections.sort(items, new FeedItemPubdateComparator()); - adapter.close(); return items; } @@ -355,7 +354,7 @@ public final class DBReader { * @return A list of FeedItems whose 'read'-attribute it set to false. */ public static List<FeedItem> getUnreadItemsList() { - Log.d(TAG, "Extracting unread items list"); + Log.d(TAG, "getUnreadItemsList() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -376,7 +375,7 @@ public final class DBReader { * @return A list of FeedItems that are considered new. */ public static List<FeedItem> getNewItemsList() { - Log.d(TAG, "getNewItemsList()"); + Log.d(TAG, "getNewItemsList() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -393,7 +392,7 @@ public final class DBReader { } public static List<FeedItem> getFavoriteItemsList() { - Log.d(TAG, "getFavoriteItemsList()"); + Log.d(TAG, "getFavoriteItemsList() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -410,7 +409,9 @@ public final class DBReader { } static LongList getFavoriteIDList() { - PodDBAdapter adapter = PodDBAdapter.getInstance().open(); + Log.d(TAG, "getFavoriteIDList() called"); + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); Cursor favoritesCursor = adapter.getFavoritesCursor(); LongList favoriteIDs = new LongList(favoritesCursor.getCount()); @@ -420,6 +421,7 @@ public final class DBReader { } while (favoritesCursor.moveToNext()); } favoritesCursor.close(); + adapter.close(); return favoriteIDs; } @@ -429,7 +431,7 @@ public final class DBReader { * @param limit The maximum number of episodes that should be loaded. */ public static List<FeedItem> getRecentlyPublishedEpisodes(int limit) { - Log.d(TAG, "Extracting recently published items list"); + Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "limit = [" + limit + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -453,7 +455,7 @@ public final class DBReader { * The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}. */ public static List<FeedItem> getPlaybackHistory() { - Log.d(TAG, "Loading playback history"); + Log.d(TAG, "getPlaybackHistory() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -482,7 +484,7 @@ public final class DBReader { * The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}. */ public static List<DownloadStatus> getDownloadLog() { - Log.d(TAG, "Extracting DownloadLog"); + Log.d(TAG, "getDownloadLog() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -496,6 +498,7 @@ public final class DBReader { } while (logCursor.moveToNext()); } logCursor.close(); + adapter.close(); Collections.sort(downloadLog, new DownloadStatusComparator()); return downloadLog; } @@ -508,7 +511,7 @@ public final class DBReader { * newest events first. */ public static List<DownloadStatus> getFeedDownloadLog(Feed feed) { - Log.d(TAG, "getFeedDownloadLog(" + feed.toString() + ")"); + Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feed + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -522,6 +525,7 @@ public final class DBReader { } while (cursor.moveToNext()); } cursor.close(); + adapter.close(); Collections.sort(downloadLog, new DownloadStatusComparator()); return downloadLog; } @@ -534,6 +538,7 @@ public final class DBReader { * @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title. */ public static List<FeedItemStatistics> getFeedStatisticsList() { + Log.d(TAG, "getFeedStatisticsList() called"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<FeedItemStatistics> result = new ArrayList<>(); @@ -558,6 +563,7 @@ public final class DBReader { * database and the items-attribute will be set correctly. */ public static Feed getFeed(final long feedId) { + Log.d(TAG, "getFeed() called with: " + "feedId = [" + feedId + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); Feed result = getFeed(feedId, adapter); @@ -566,7 +572,6 @@ public final class DBReader { } static Feed getFeed(final long feedId, PodDBAdapter adapter) { - Log.d(TAG, "Loading feed with id " + feedId); Feed feed = null; Cursor feedCursor = adapter.getFeedCursor(feedId); @@ -635,7 +640,7 @@ public final class DBReader { * as well as chapter marks of the FeedItem will also be loaded from the database. */ public static FeedItem getFeedItem(final long itemId) { - Log.d(TAG, "Loading feeditem with id " + itemId); + Log.d(TAG, "getFeedItem() called with: " + "itemId = [" + itemId + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -671,8 +676,7 @@ public final class DBReader { * as well as chapter marks of the FeedItems will also be loaded from the database. */ public static List<FeedItem> getFeedItems(final long... itemIds) { - Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ",")); - + Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + itemIds + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<FeedItem> items = getFeedItems(adapter, itemIds); @@ -688,7 +692,7 @@ public final class DBReader { * @return Credentials in format "<Username>:<Password>", empty String if no authorization given */ public static String getImageAuthentication(final String imageUrl) { - Log.d(TAG, "Loading credentials for image with URL " + imageUrl); + Log.d(TAG, "getImageAuthentication() called with: " + "imageUrl = [" + imageUrl + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -728,7 +732,7 @@ public final class DBReader { * as well as chapter marks of the FeedItem will also be loaded from the database. */ public static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl) { - Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl); + Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -743,6 +747,7 @@ public final class DBReader { * @param item The FeedItem */ public static void loadExtraInformationOfFeedItem(final FeedItem item) { + Log.d(TAG, "loadExtraInformationOfFeedItem() called with: " + "item = [" + item + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); Cursor extraCursor = adapter.getExtraInformationOfItem(item); @@ -766,6 +771,7 @@ public final class DBReader { * @param item The FeedItem */ public static void loadChaptersOfFeedItem(final FeedItem item) { + Log.d(TAG, "loadChaptersOfFeedItem() called with: " + "item = [" + item + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); loadChaptersOfFeedItem(adapter, item); @@ -820,6 +826,7 @@ public final class DBReader { * @return The number of downloaded episodes. */ public static int getNumberOfDownloadedEpisodes() { + Log.d(TAG, "getNumberOfDownloadedEpisodes() called with: " + ""); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); final int result = adapter.getNumberOfDownloadedEpisodes(); @@ -834,6 +841,7 @@ public final class DBReader { * @return The found object */ public static FeedImage getFeedImage(final long imageId) { + Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); FeedImage result = getFeedImage(adapter, imageId); @@ -844,21 +852,34 @@ public final class DBReader { /** * Searches the DB for a FeedImage of the given id. * - * @param id The id of the object + * @param imageId The id of the object * @return The found object */ - static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { - Cursor cursor = adapter.getImageCursor(id); + private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) { + return getFeedImages(adapter, imageId).get(imageId); + } + + /** + * Searches the DB for a FeedImage of the given id. + * + * @param ids The id of the object + * @return The found object + */ + private static Map<Long,FeedImage> getFeedImages(PodDBAdapter adapter, final long... ids) { + Cursor cursor = adapter.getImageCursor(ids); + Map<Long, FeedImage> result = new HashMap<>(cursor.getCount()); try { if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { - return null; + return Collections.emptyMap(); } - FeedImage image = FeedImage.fromCursor(cursor); - image.setId(id); - return image; + do { + FeedImage image = FeedImage.fromCursor(cursor); + result.put(image.getId(), image); + } while(cursor.moveToNext()); } finally { cursor.close(); } + return result; } /** @@ -897,6 +918,7 @@ public final class DBReader { * @return The flattr queue as a List. */ public static List<FlattrThing> getFlattrQueue() { + Log.d(TAG, "getFlattrQueue() called with: " + ""); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<FlattrThing> result = new ArrayList<>(); @@ -927,6 +949,7 @@ public final class DBReader { * */ public static NavDrawerData getNavDrawerData() { + Log.d(TAG, "getNavDrawerData() called with: " + ""); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<Feed> feeds = getFeedList(adapter); 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 14683506e..49a62da8c 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 @@ -766,6 +766,7 @@ public class DBWriter { adapter.open(); adapter.setSingleFeedItem(item); adapter.close(); + EventBus.getDefault().post(FeedItemEvent.updated(item)); }); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java index b13f7a0cb..318060abc 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedFile; import de.danoeh.antennapod.core.feed.FeedMedia; @@ -85,7 +84,7 @@ public class DownloadRequester { Intent launchIntent = new Intent(context, DownloadService.class); launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); context.startService(launchIntent); - EventDistributor.getInstance().sendDownloadQueuedBroadcast(); + return true; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 6ccb1d226..c67fb956a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -10,14 +10,15 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.media.MediaMetadataRetriever; +import android.os.Build; import android.text.TextUtils; import android.util.Log; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.ProgressEvent; @@ -188,6 +189,15 @@ public class PodDBAdapter { + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " (" + KEY_IMAGE + ")"; + public static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS " + + TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " (" + + KEY_PUBDATE + ")"; + + public static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS " + + TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " (" + + KEY_READ + ")"; + + public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX " + TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " (" + KEY_FEEDITEM + ")"; @@ -270,10 +280,10 @@ public class PodDBAdapter { KEY_CONTENT_ENCODED, KEY_FEED}; - private SQLiteDatabase db; + private static SQLiteDatabase db; private static Context context; private static PodDBHelper dbHelper; - private static AtomicInteger counter = new AtomicInteger(0); + private static int counter = 0; public static void init(Context context) { PodDBAdapter.context = context.getApplicationContext(); @@ -288,12 +298,15 @@ public class PodDBAdapter { private PodDBAdapter() {} - public PodDBAdapter open() { - counter.incrementAndGet(); + public synchronized PodDBAdapter open() { + counter++; if (db == null || !db.isOpen() || db.isReadOnly()) { Log.v(TAG, "Opening DB"); try { db = dbHelper.getWritableDatabase(); + if(Build.VERSION.SDK_INT >= 11) { + db.enableWriteAheadLogging(); + } } catch (SQLException ex) { Log.e(TAG, Log.getStackTraceString(ex)); db = dbHelper.getReadableDatabase(); @@ -302,12 +315,13 @@ public class PodDBAdapter { return this; } - public void close() { - if(counter.decrementAndGet() == 0) { + public synchronized void close() { + counter--; + if(counter == 0) { Log.v(TAG, "Closing DB"); db.close(); + db = null; } - db = null; } public static boolean deleteDatabase() { @@ -990,14 +1004,16 @@ public class PodDBAdapter { } /** - * Returns a cursor for a DB query in the FeedImages table for a given ID. + * Returns a cursor for a DB query in the FeedImages table for given IDs. * - * @param id ID of the FeedImage + * @param ids IDs of the FeedImages * @return The cursor of the query */ - public final Cursor getImageCursor(final long id) { - Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?", - new String[]{String.valueOf(id)}, null, null, null); + public final Cursor getImageCursor(long... ids) { + String sql = "SELECT * FROM " + TABLE_NAME_FEED_IMAGES + + " WHERE " + KEY_ID + " IN (" + StringUtils.join(ids, ',') + ")"; + Cursor c = db.rawQuery(sql, null); + return c; } @@ -1138,26 +1154,26 @@ public class PodDBAdapter { return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null); } - public final Cursor getFeedMediaCursorByItemID(String... mediaIds) { - int length = mediaIds.length; + public final Cursor getFeedMediaCursor(String... itemIds) { + int length = itemIds.length; if (length > IN_OPERATOR_MAXIMUM) { Log.w(TAG, "Length of id array is larger than " + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors"); int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1; Cursor[] cursors = new Cursor[numCursors]; for (int i = 0; i < numCursors; i++) { - int neededLength = 0; - String[] parts = null; + int neededLength; + String[] parts; final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM; if (elementsLeft >= IN_OPERATOR_MAXIMUM) { neededLength = IN_OPERATOR_MAXIMUM; - parts = Arrays.copyOfRange(mediaIds, i + parts = Arrays.copyOfRange(itemIds, i * IN_OPERATOR_MAXIMUM, (i + 1) * IN_OPERATOR_MAXIMUM); } else { neededLength = elementsLeft; - parts = Arrays.copyOfRange(mediaIds, i + parts = Arrays.copyOfRange(itemIds, i * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM) + neededLength); } @@ -1169,7 +1185,7 @@ public class PodDBAdapter { return new MergeCursor(cursors); } else { return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN " - + buildInOperator(length), mediaIds, null, null, null); + + buildInOperator(length), itemIds, null, null, null); } } @@ -1442,7 +1458,7 @@ public class PodDBAdapter { */ private static class PodDBHelper extends SQLiteOpenHelper { - private final static int VERSION = 1040002; + private final static int VERSION = 1040013; private Context context; @@ -1472,6 +1488,8 @@ public class PodDBAdapter { db.execSQL(CREATE_INDEX_FEEDITEMS_FEED); db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE); + db.execSQL(CREATE_INDEX_FEEDITEMS_PUBDATE); + db.execSQL(CREATE_INDEX_FEEDITEMS_READ); db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM); db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM); db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM); @@ -1680,6 +1698,11 @@ public class PodDBAdapter { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0"); } + if(oldVersion < 1040013) { + db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE); + db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ); + } + EventBus.getDefault().post(ProgressEvent.end()); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java index 10ffd4bec..6ed8b820e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java @@ -177,6 +177,28 @@ public final class LongList { } /** + * Removes values from this list. + * + * @param values values to remove + */ + public void removeAll(long[] values) { + for(long value : values) { + remove(value); + } + } + + /** + * Removes values from this list. + * + * @param list List with values to remove + */ + public void removeAll(LongList list) { + for(long value : list.values) { + remove(value); + } + } + + /** * Removes an element at a given index, shifting elements at greater * indicies down one. * |