mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-12-01 12:31:45 +00:00
1554 lines
44 KiB
Java
1554 lines
44 KiB
Java
package de.danoeh.antennapod.feed;
|
|
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ThreadFactory;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.database.Cursor;
|
|
import android.os.AsyncTask;
|
|
import android.os.Handler;
|
|
import android.preference.PreferenceManager;
|
|
import android.util.Log;
|
|
import de.danoeh.antennapod.AppConfig;
|
|
import de.danoeh.antennapod.PodcastApp;
|
|
import de.danoeh.antennapod.asynctask.DownloadStatus;
|
|
import de.danoeh.antennapod.service.PlaybackService;
|
|
import de.danoeh.antennapod.storage.DownloadRequestException;
|
|
import de.danoeh.antennapod.storage.DownloadRequester;
|
|
import de.danoeh.antennapod.storage.PodDBAdapter;
|
|
import de.danoeh.antennapod.util.DownloadError;
|
|
import de.danoeh.antennapod.util.FeedtitleComparator;
|
|
import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
|
|
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
|
|
import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator;
|
|
|
|
/**
|
|
* Singleton class Manages all feeds, categories and feeditems
|
|
*
|
|
*
|
|
* */
|
|
public class FeedManager {
|
|
private static final String TAG = "FeedManager";
|
|
|
|
public static final String ACTION_FEED_LIST_UPDATE = "de.danoeh.antennapod.action.feed.feedlistUpdate";
|
|
public static final String ACTION_UNREAD_ITEMS_UPDATE = "de.danoeh.antennapod.action.feed.unreadItemsUpdate";
|
|
public static final String ACTION_QUEUE_UPDATE = "de.danoeh.antennapod.action.feed.queueUpdate";
|
|
public static final String ACTION_DOWNLOADLOG_UPDATE = "de.danoeh.antennapod.action.feed.downloadLogUpdate";
|
|
public static final String ACTION_PLAYBACK_HISTORY_UPDATE = "de.danoeh.antennapod.action.feed.playbackHistoryUpdate";
|
|
public static final String EXTRA_FEED_ITEM_ID = "de.danoeh.antennapod.extra.feed.feedItemId";
|
|
public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feed.feedId";
|
|
|
|
/** Number of completed Download status entries to store. */
|
|
private static final int DOWNLOAD_LOG_SIZE = 50;
|
|
|
|
private static FeedManager singleton;
|
|
|
|
private List<Feed> feeds;
|
|
|
|
/** Contains all items where 'read' is false */
|
|
private List<FeedItem> unreadItems;
|
|
|
|
/** Contains completed Download status entries */
|
|
private ArrayList<DownloadStatus> downloadLog;
|
|
|
|
/** Contains the queue of items to be played. */
|
|
private List<FeedItem> queue;
|
|
|
|
/** Contains the last played items */
|
|
private List<FeedItem> playbackHistory;
|
|
|
|
/** Maximum number of items in the playback history. */
|
|
private static final int PLAYBACK_HISTORY_SIZE = 15;
|
|
|
|
private DownloadRequester requester;
|
|
|
|
/** Should be used to change the content of the arrays from another thread. */
|
|
private Handler contentChanger;
|
|
/** Ensures that there are no parallel db operations. */
|
|
private Executor dbExec;
|
|
|
|
/** Prevents user from starting several feed updates at the same time. */
|
|
private static boolean isStartingFeedRefresh = false;
|
|
|
|
private FeedManager() {
|
|
feeds = Collections.synchronizedList(new ArrayList<Feed>());
|
|
unreadItems = Collections.synchronizedList(new ArrayList<FeedItem>());
|
|
requester = DownloadRequester.getInstance();
|
|
downloadLog = new ArrayList<DownloadStatus>();
|
|
queue = Collections.synchronizedList(new ArrayList<FeedItem>());
|
|
playbackHistory = Collections
|
|
.synchronizedList(new ArrayList<FeedItem>());
|
|
contentChanger = new Handler();
|
|
dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
|
|
|
@Override
|
|
public Thread newThread(Runnable r) {
|
|
Thread t = new Thread(r);
|
|
t.setPriority(Thread.MIN_PRIORITY);
|
|
return t;
|
|
}
|
|
});
|
|
}
|
|
|
|
public static FeedManager getInstance() {
|
|
if (singleton == null) {
|
|
singleton = new FeedManager();
|
|
}
|
|
return singleton;
|
|
}
|
|
|
|
/**
|
|
* Play FeedMedia and start the playback service + launch Mediaplayer
|
|
* Activity.
|
|
*
|
|
* @param context
|
|
* for starting the playbackservice
|
|
* @param media
|
|
* that shall be played
|
|
* @param showPlayer
|
|
* if Mediaplayer activity shall be started
|
|
* @param startWhenPrepared
|
|
* if Mediaplayer shall be started after it has been prepared
|
|
*/
|
|
public void playMedia(Context context, FeedMedia media, boolean showPlayer,
|
|
boolean startWhenPrepared, boolean shouldStream) {
|
|
// Start playback Service
|
|
Intent launchIntent = new Intent(context, PlaybackService.class);
|
|
launchIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, media.getId());
|
|
launchIntent.putExtra(PlaybackService.EXTRA_FEED_ID, media.getItem()
|
|
.getFeed().getId());
|
|
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
|
|
startWhenPrepared);
|
|
launchIntent
|
|
.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
|
|
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true);
|
|
context.startService(launchIntent);
|
|
if (showPlayer) {
|
|
// Launch Mediaplayer
|
|
context.startActivity(PlaybackService.getPlayerActivityIntent(
|
|
context, media));
|
|
}
|
|
}
|
|
|
|
/** Remove media item that has been downloaded. */
|
|
public boolean deleteFeedMedia(Context context, FeedMedia media) {
|
|
boolean result = false;
|
|
if (media.isDownloaded()) {
|
|
File mediaFile = new File(media.file_url);
|
|
if (mediaFile.exists()) {
|
|
result = mediaFile.delete();
|
|
}
|
|
media.setDownloaded(false);
|
|
media.setFile_url(null);
|
|
setFeedMedia(context, media);
|
|
|
|
SharedPreferences prefs = PreferenceManager
|
|
.getDefaultSharedPreferences(context);
|
|
final long lastPlayedId = prefs.getLong(
|
|
PlaybackService.PREF_LAST_PLAYED_ID, -1);
|
|
if (media.getId() == lastPlayedId) {
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putBoolean(PlaybackService.PREF_LAST_IS_STREAM, true);
|
|
editor.commit();
|
|
}
|
|
if (lastPlayedId == media.getId()) {
|
|
context.sendBroadcast(new Intent(
|
|
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
|
|
}
|
|
}
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Deleting File. Result: " + result);
|
|
return result;
|
|
}
|
|
|
|
/** Remove a feed with all its items and media files and its image. */
|
|
public void deleteFeed(final Context context, final Feed feed) {
|
|
SharedPreferences prefs = PreferenceManager
|
|
.getDefaultSharedPreferences(context.getApplicationContext());
|
|
long lastPlayedFeed = prefs.getLong(
|
|
PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1);
|
|
if (lastPlayedFeed == feed.getId()) {
|
|
context.sendBroadcast(new Intent(
|
|
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putLong(PlaybackService.PREF_LAST_PLAYED_ID, -1);
|
|
editor.putLong(PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1);
|
|
editor.commit();
|
|
}
|
|
|
|
contentChanger.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
feeds.remove(feed);
|
|
sendFeedUpdateBroadcast(context);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
DownloadRequester requester = DownloadRequester
|
|
.getInstance();
|
|
adapter.open();
|
|
// delete image file
|
|
if (feed.getImage() != null) {
|
|
if (feed.getImage().isDownloaded()
|
|
&& feed.getImage().getFile_url() != null) {
|
|
File imageFile = new File(feed.getImage()
|
|
.getFile_url());
|
|
imageFile.delete();
|
|
} else if (requester.isDownloadingFile(feed
|
|
.getImage())) {
|
|
requester.cancelDownload(context,
|
|
feed.getImage());
|
|
}
|
|
}
|
|
// delete stored media files and mark them as read
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getState() == FeedItem.State.NEW) {
|
|
unreadItems.remove(item);
|
|
}
|
|
if (queue.contains(item)) {
|
|
removeQueueItem(item, adapter);
|
|
}
|
|
removeItemFromPlaybackHistory(context, item);
|
|
if (item.getMedia() != null
|
|
&& item.getMedia().isDownloaded()) {
|
|
File mediaFile = new File(item.getMedia()
|
|
.getFile_url());
|
|
mediaFile.delete();
|
|
} else if (item.getMedia() != null
|
|
&& requester.isDownloadingFile(item
|
|
.getMedia())) {
|
|
requester.cancelDownload(context,
|
|
item.getMedia());
|
|
}
|
|
}
|
|
|
|
adapter.removeFeed(feed);
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
private void sendUnreadItemsUpdateBroadcast(Context context, FeedItem item) {
|
|
Intent update = new Intent(ACTION_UNREAD_ITEMS_UPDATE);
|
|
if (item != null) {
|
|
update.putExtra(EXTRA_FEED_ID, item.getFeed().getId());
|
|
update.putExtra(EXTRA_FEED_ITEM_ID, item.getId());
|
|
}
|
|
context.sendBroadcast(update);
|
|
}
|
|
|
|
private void sendQueueUpdateBroadcast(Context context, FeedItem item) {
|
|
Intent update = new Intent(ACTION_QUEUE_UPDATE);
|
|
if (item != null) {
|
|
update.putExtra(EXTRA_FEED_ID, item.getFeed().getId());
|
|
update.putExtra(EXTRA_FEED_ITEM_ID, item.getId());
|
|
}
|
|
context.sendBroadcast(update);
|
|
}
|
|
|
|
private void sendFeedUpdateBroadcast(Context context) {
|
|
context.sendBroadcast(new Intent(ACTION_FEED_LIST_UPDATE));
|
|
}
|
|
|
|
private void sendPlaybackHistoryUpdateBroadcast(Context context) {
|
|
context.sendBroadcast(new Intent(ACTION_PLAYBACK_HISTORY_UPDATE));
|
|
}
|
|
|
|
/**
|
|
* Makes sure that playback history is sorted and is not larger than
|
|
* PLAYBACK_HISTORY_SIZE.
|
|
*
|
|
* @return an array of all feeditems that were remove from the playback
|
|
* history or null if no items were removed.
|
|
*/
|
|
private FeedItem[] cleanupPlaybackHistory() {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Cleaning up playback history.");
|
|
|
|
Collections.sort(playbackHistory,
|
|
new PlaybackCompletionDateComparator());
|
|
final int initialSize = playbackHistory.size();
|
|
if (initialSize > PLAYBACK_HISTORY_SIZE) {
|
|
FeedItem[] removed = new FeedItem[initialSize
|
|
- PLAYBACK_HISTORY_SIZE];
|
|
|
|
for (int i = 0; i < removed.length; i++) {
|
|
removed[i] = playbackHistory.remove(playbackHistory.size() - 1);
|
|
}
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Removed " + removed.length
|
|
+ " items from playback history.");
|
|
return removed;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Executes cleanupPlaybackHistory and deletes the playbackCompletionDate of
|
|
* all item that were removed from the history.
|
|
*/
|
|
private void cleanupPlaybackHistoryWithDBCleanup(final Context context) {
|
|
final FeedItem[] removedItems = cleanupPlaybackHistory();
|
|
if (removedItems != null) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
for (FeedItem item : removedItems) {
|
|
if (item.getMedia() != null) {
|
|
item.getMedia().setPlaybackCompletionDate(null);
|
|
adapter.setMedia(item.getMedia());
|
|
}
|
|
}
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void clearPlaybackHistory(final Context context) {
|
|
if (!playbackHistory.isEmpty()) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Clearing playback history.");
|
|
final FeedItem[] items = playbackHistory
|
|
.toArray(new FeedItem[playbackHistory.size()]);
|
|
playbackHistory.clear();
|
|
sendPlaybackHistoryUpdateBroadcast(context);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
for (FeedItem item : items) {
|
|
if (item.getMedia() != null
|
|
&& item.getMedia().getPlaybackCompletionDate() != null) {
|
|
item.getMedia().setPlaybackCompletionDate(null);
|
|
adapter.setMedia(item.getMedia());
|
|
}
|
|
}
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void addItemToPlaybackHistory(Context context, FeedItem item) {
|
|
if (item.getMedia() != null
|
|
&& item.getMedia().getPlaybackCompletionDate() != null) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Adding new item to playback history");
|
|
if (!playbackHistory.contains(item)) {
|
|
playbackHistory.add(item);
|
|
}
|
|
cleanupPlaybackHistoryWithDBCleanup(context);
|
|
sendPlaybackHistoryUpdateBroadcast(context);
|
|
}
|
|
}
|
|
|
|
private void removeItemFromPlaybackHistory(Context context, FeedItem item) {
|
|
playbackHistory.remove(item);
|
|
sendPlaybackHistoryUpdateBroadcast(context);
|
|
}
|
|
|
|
/**
|
|
* Sets the 'read'-attribute of a FeedItem. Should be used by all Classes
|
|
* instead of the setters of FeedItem.
|
|
*/
|
|
public void markItemRead(final Context context, final FeedItem item,
|
|
final boolean read, boolean resetMediaPosition) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Setting item with title " + item.getTitle()
|
|
+ " as read/unread");
|
|
|
|
item.setRead(read);
|
|
if (item.hasMedia() && resetMediaPosition) {
|
|
item.getMedia().setPosition(0);
|
|
}
|
|
setFeedItem(context, item);
|
|
if (item.hasMedia() && resetMediaPosition)
|
|
setFeedMedia(context, item.getMedia());
|
|
|
|
contentChanger.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (read == true) {
|
|
unreadItems.remove(item);
|
|
} else {
|
|
unreadItems.add(item);
|
|
Collections.sort(unreadItems,
|
|
new FeedItemPubdateComparator());
|
|
}
|
|
sendUnreadItemsUpdateBroadcast(context, item);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the 'read' attribute of all FeedItems of a specific feed to true
|
|
*
|
|
* @param context
|
|
*/
|
|
public void markFeedRead(Context context, Feed feed) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (unreadItems.contains(item)) {
|
|
markItemRead(context, item, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Marks all items in the unread items list as read */
|
|
public void markAllItemsRead(final Context context) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "marking all items as read");
|
|
for (FeedItem item : unreadItems) {
|
|
item.setRead(true);
|
|
}
|
|
final ArrayList<FeedItem> unreadItemsCopy = new ArrayList<FeedItem>(
|
|
unreadItems);
|
|
unreadItems.clear();
|
|
sendUnreadItemsUpdateBroadcast(context, null);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
for (FeedItem item : unreadItemsCopy) {
|
|
setFeedItem(item, adapter);
|
|
if (item.hasMedia())
|
|
setFeedMedia(context, item.getMedia());
|
|
}
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
public void refreshAllFeeds(final Context context) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Refreshing all feeds.");
|
|
if (!isStartingFeedRefresh) {
|
|
isStartingFeedRefresh = true;
|
|
AsyncTask<Void, Void, Void> updateWorker = new AsyncTask<Void, Void, Void>() {
|
|
|
|
@Override
|
|
protected void onPostExecute(Void result) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG,
|
|
"All feeds have been sent to the downloadmanager");
|
|
isStartingFeedRefresh = false;
|
|
}
|
|
|
|
@Override
|
|
protected Void doInBackground(Void... params) {
|
|
for (Feed feed : feeds) {
|
|
try {
|
|
refreshFeed(context, feed);
|
|
} catch (DownloadRequestException e) {
|
|
e.printStackTrace();
|
|
addDownloadStatus(
|
|
context,
|
|
new DownloadStatus(feed, feed
|
|
.getHumanReadableIdentifier(),
|
|
DownloadError.ERROR_REQUEST_ERROR,
|
|
false, e.getMessage()));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
};
|
|
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
|
|
updateWorker.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
} else {
|
|
updateWorker.execute();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Notifies the feed manager that the an image file is invalid. It will try
|
|
* to redownload it
|
|
*/
|
|
public void notifyInvalidImageFile(Context context, FeedImage image) {
|
|
Log.i(TAG,
|
|
"The feedmanager was notified about an invalid image download. It will now try to redownload the image file");
|
|
try {
|
|
requester.downloadImage(context, image);
|
|
} catch (DownloadRequestException e) {
|
|
e.printStackTrace();
|
|
Log.w(TAG, "Failed to download invalid feed image");
|
|
}
|
|
}
|
|
|
|
public void refreshFeed(Context context, Feed feed)
|
|
throws DownloadRequestException {
|
|
requester.downloadFeed(context, new Feed(feed.getDownload_url(),
|
|
new Date(), feed.getTitle()));
|
|
}
|
|
|
|
public void addDownloadStatus(final Context context,
|
|
final DownloadStatus status) {
|
|
contentChanger.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
downloadLog.add(status);
|
|
Collections.sort(downloadLog, new DownloadStatusComparator());
|
|
final DownloadStatus removedStatus;
|
|
if (downloadLog.size() > DOWNLOAD_LOG_SIZE) {
|
|
removedStatus = downloadLog.remove(downloadLog.size() - 1);
|
|
} else {
|
|
removedStatus = null;
|
|
}
|
|
context.sendBroadcast(new Intent(ACTION_DOWNLOADLOG_UPDATE));
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
if (removedStatus != null) {
|
|
adapter.removeDownloadStatus(removedStatus);
|
|
}
|
|
adapter.setDownloadStatus(status);
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
public void downloadAllItemsInQueue(final Context context) {
|
|
if (!queue.isEmpty()) {
|
|
try {
|
|
downloadFeedItem(context,
|
|
queue.toArray(new FeedItem[queue.size()]));
|
|
} catch (DownloadRequestException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void downloadFeedItem(final Context context, FeedItem... items)
|
|
throws DownloadRequestException {
|
|
boolean autoQueue = PreferenceManager.getDefaultSharedPreferences(
|
|
context.getApplicationContext()).getBoolean(
|
|
PodcastApp.PREF_AUTO_QUEUE, true);
|
|
List<FeedItem> addToQueue = new ArrayList<FeedItem>();
|
|
|
|
for (FeedItem item : items) {
|
|
if (item.getMedia() != null
|
|
&& !requester.isDownloadingFile(item.getMedia())
|
|
&& !item.getMedia().isDownloaded()) {
|
|
if (items.length > 1) {
|
|
try {
|
|
requester.downloadMedia(context, item.getMedia());
|
|
} catch (DownloadRequestException e) {
|
|
e.printStackTrace();
|
|
addDownloadStatus(context,
|
|
new DownloadStatus(item.getMedia(), item
|
|
.getMedia()
|
|
.getHumanReadableIdentifier(),
|
|
DownloadError.ERROR_REQUEST_ERROR,
|
|
false, e.getMessage()));
|
|
}
|
|
} else {
|
|
requester.downloadMedia(context, item.getMedia());
|
|
}
|
|
addToQueue.add(item);
|
|
}
|
|
}
|
|
if (autoQueue) {
|
|
addQueueItem(context,
|
|
addToQueue.toArray(new FeedItem[addToQueue.size()]));
|
|
}
|
|
}
|
|
|
|
public void enqueueAllNewItems(final Context context) {
|
|
if (!unreadItems.isEmpty()) {
|
|
addQueueItem(context,
|
|
unreadItems.toArray(new FeedItem[unreadItems.size()]));
|
|
markAllItemsRead(context);
|
|
}
|
|
}
|
|
|
|
public void addQueueItem(final Context context, final FeedItem... items) {
|
|
if (items.length > 0) {
|
|
contentChanger.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
for (FeedItem item : items) {
|
|
if (!queue.contains(item)) {
|
|
queue.add(item);
|
|
}
|
|
}
|
|
sendQueueUpdateBroadcast(context, items[0]);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setQueue(queue);
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Return the item that comes after this item in the queue or null if this
|
|
* item is not in the queue or if this item has no successor.
|
|
*/
|
|
public FeedItem getQueueSuccessorOfItem(FeedItem item) {
|
|
if (isInQueue(item)) {
|
|
int itemIndex = queue.indexOf(item);
|
|
if (itemIndex != -1 && itemIndex < (queue.size() - 1)) {
|
|
return queue.get(itemIndex + 1);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Removes all items in queue */
|
|
public void clearQueue(final Context context) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Clearing queue");
|
|
queue.clear();
|
|
sendQueueUpdateBroadcast(context, null);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setQueue(queue);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/** Uses external adapter. */
|
|
public void removeQueueItem(FeedItem item, PodDBAdapter adapter) {
|
|
boolean removed = queue.remove(item);
|
|
if (removed) {
|
|
adapter.setQueue(queue);
|
|
}
|
|
|
|
}
|
|
|
|
/** Uses its own adapter. */
|
|
public void removeQueueItem(final Context context, FeedItem item) {
|
|
boolean removed = queue.remove(item);
|
|
if (removed) {
|
|
autoDeleteIfPossible(context, item.getMedia());
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setQueue(queue);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
sendQueueUpdateBroadcast(context, item);
|
|
}
|
|
|
|
/**
|
|
* Delete the episode of this FeedMedia object if auto-delete is enabled and
|
|
* it is not the last played media or it is the last played media and
|
|
* playback has been completed.
|
|
*/
|
|
public void autoDeleteIfPossible(Context context, FeedMedia media) {
|
|
if (media != null) {
|
|
SharedPreferences prefs = PreferenceManager
|
|
.getDefaultSharedPreferences(context
|
|
.getApplicationContext());
|
|
boolean autoDelete = prefs.getBoolean(PodcastApp.PREF_AUTO_DELETE,
|
|
false);
|
|
if (autoDelete) {
|
|
long lastPlayedId = prefs.getLong(
|
|
PlaybackService.PREF_LAST_PLAYED_ID, -1);
|
|
long autoDeleteId = prefs.getLong(
|
|
PlaybackService.PREF_AUTODELETE_MEDIA_ID, -1);
|
|
boolean playbackCompleted = prefs
|
|
.getBoolean(
|
|
PlaybackService.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED,
|
|
false);
|
|
if ((media.getId() != lastPlayedId)
|
|
&& ((media.getId() != autoDeleteId) || (media.getId() == autoDeleteId && playbackCompleted))) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Performing auto-cleanup");
|
|
deleteFeedMedia(context, media);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putLong(PlaybackService.PREF_AUTODELETE_MEDIA_ID, -1);
|
|
editor.commit();
|
|
} else {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Didn't do auto-cleanup");
|
|
}
|
|
} else {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Auto-delete preference is disabled");
|
|
}
|
|
} else {
|
|
Log.e(TAG, "Could not do auto-cleanup: media was null");
|
|
}
|
|
}
|
|
|
|
public void moveQueueItem(final Context context, FeedItem item, int delta) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Moving queue item");
|
|
int itemIndex = queue.indexOf(item);
|
|
int newIndex = itemIndex + delta;
|
|
if (newIndex >= 0 && newIndex < queue.size()) {
|
|
FeedItem oldItem = queue.set(newIndex, item);
|
|
queue.set(itemIndex, oldItem);
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setQueue(queue);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
sendQueueUpdateBroadcast(context, item);
|
|
}
|
|
|
|
public boolean isInQueue(FeedItem item) {
|
|
return queue.contains(item);
|
|
}
|
|
|
|
public FeedItem getFirstQueueItem() {
|
|
if (queue.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
return queue.get(0);
|
|
}
|
|
}
|
|
|
|
private void addNewFeed(final Context context, final Feed feed) {
|
|
contentChanger.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
feeds.add(feed);
|
|
Collections.sort(feeds, new FeedtitleComparator());
|
|
sendFeedUpdateBroadcast(context);
|
|
}
|
|
});
|
|
setCompleteFeed(context, feed);
|
|
}
|
|
|
|
/**
|
|
* Updates an existing feed or adds it as a new one if it doesn't exist.
|
|
*
|
|
* @return The saved Feed with a database ID
|
|
*/
|
|
public Feed updateFeed(Context context, final Feed newFeed) {
|
|
// Look up feed in the feedslist
|
|
final Feed savedFeed = searchFeedByIdentifyingValue(newFeed
|
|
.getIdentifyingValue());
|
|
if (savedFeed == null) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG,
|
|
"Found no existing Feed with title "
|
|
+ newFeed.getTitle() + ". Adding as new one.");
|
|
// Add a new Feed
|
|
addNewFeed(context, newFeed);
|
|
return newFeed;
|
|
} else {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Feed with title " + newFeed.getTitle()
|
|
+ " already exists. Syncing new with existing one.");
|
|
if (savedFeed.compareWithOther(newFeed)) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG,
|
|
"Feed has updated attribute values. Updating old feed's attributes");
|
|
savedFeed.updateFromOther(newFeed);
|
|
}
|
|
// Look for new or updated Items
|
|
for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
|
|
final FeedItem item = newFeed.getItems().get(idx);
|
|
FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
|
|
item.getIdentifyingValue());
|
|
if (oldItem == null) {
|
|
// item is new
|
|
final int i = idx;
|
|
item.setFeed(savedFeed);
|
|
contentChanger.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
savedFeed.getItems().add(i, item);
|
|
|
|
}
|
|
});
|
|
markItemRead(context, item, false, false);
|
|
} else {
|
|
oldItem.updateFromOther(item);
|
|
}
|
|
}
|
|
// update attributes
|
|
savedFeed.setLastUpdate(newFeed.getLastUpdate());
|
|
savedFeed.setType(newFeed.getType());
|
|
setCompleteFeed(context, savedFeed);
|
|
return savedFeed;
|
|
}
|
|
|
|
}
|
|
|
|
/** Get a Feed by its link */
|
|
private Feed searchFeedByIdentifyingValue(String identifier) {
|
|
for (Feed feed : feeds) {
|
|
if (feed.getIdentifyingValue().equals(identifier)) {
|
|
return feed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns true if a feed with the given download link is already in the
|
|
* feedlist.
|
|
*/
|
|
public boolean feedExists(String downloadUrl) {
|
|
for (Feed feed : feeds) {
|
|
if (feed.getDownload_url().equals(downloadUrl)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Get a FeedItem by its identifying value. */
|
|
private FeedItem searchFeedItemByIdentifyingValue(Feed feed,
|
|
String identifier) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getIdentifyingValue().equals(identifier)) {
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Updates Information of an existing Feed. Uses external adapter. */
|
|
public void setFeed(Feed feed, PodDBAdapter adapter) {
|
|
if (adapter != null) {
|
|
adapter.setFeed(feed);
|
|
feed.cacheDescriptionsOfItems();
|
|
} else {
|
|
Log.w(TAG, "Adapter in setFeed was null");
|
|
}
|
|
}
|
|
|
|
/** Updates Information of an existing Feeditem. Uses external adapter. */
|
|
public void setFeedItem(FeedItem item, PodDBAdapter adapter) {
|
|
if (adapter != null) {
|
|
adapter.setSingleFeedItem(item);
|
|
} else {
|
|
Log.w(TAG, "Adapter in setFeedItem was null");
|
|
}
|
|
}
|
|
|
|
/** Updates Information of an existing Feedimage. Uses external adapter. */
|
|
public void setFeedImage(FeedImage image, PodDBAdapter adapter) {
|
|
if (adapter != null) {
|
|
adapter.setImage(image);
|
|
} else {
|
|
Log.w(TAG, "Adapter in setFeedImage was null");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates Information of an existing Feedmedia object. Uses external
|
|
* adapter.
|
|
*/
|
|
public void setFeedImage(FeedMedia media, PodDBAdapter adapter) {
|
|
if (adapter != null) {
|
|
adapter.setMedia(media);
|
|
} else {
|
|
Log.w(TAG, "Adapter in setFeedMedia was null");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates Information of an existing Feed. Creates and opens its own
|
|
* adapter.
|
|
*/
|
|
public void setFeed(final Context context, final Feed feed) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setFeed(feed);
|
|
feed.cacheDescriptionsOfItems();
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates Information of an existing Feed and its FeedItems. Creates and opens its own
|
|
* adapter.
|
|
*/
|
|
public void setCompleteFeed(final Context context, final Feed feed) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setCompleteFeed(feed);
|
|
feed.cacheDescriptionsOfItems();
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates information of an existing FeedItem. Creates and opens its own
|
|
* adapter.
|
|
*/
|
|
public void setFeedItem(final Context context, final FeedItem item) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setSingleFeedItem(item);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates information of an existing FeedImage. Creates and opens its own
|
|
* adapter.
|
|
*/
|
|
public void setFeedImage(final Context context, final FeedImage image) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setImage(image);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates information of an existing FeedMedia object. Creates and opens
|
|
* its own adapter.
|
|
*/
|
|
public void setFeedMedia(final Context context, final FeedMedia media) {
|
|
dbExec.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
adapter.setMedia(media);
|
|
adapter.close();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/** Get a Feed by its id */
|
|
public Feed getFeed(long id) {
|
|
for (Feed f : feeds) {
|
|
if (f.id == id) {
|
|
return f;
|
|
}
|
|
}
|
|
Log.e(TAG, "Couldn't find Feed with id " + id);
|
|
return null;
|
|
}
|
|
|
|
/** Get a Feed Image by its id */
|
|
public FeedImage getFeedImage(long id) {
|
|
for (Feed f : feeds) {
|
|
FeedImage image = f.getImage();
|
|
if (image != null && image.getId() == id) {
|
|
return image;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Get a Feed Item by its id and its feed */
|
|
public FeedItem getFeedItem(long id, Feed feed) {
|
|
if (feed != null) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getId() == id) {
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
Log.e(TAG, "Couldn't find FeedItem with id " + id);
|
|
return null;
|
|
}
|
|
|
|
/** Get a FeedItem by its id and the id of its feed. */
|
|
public FeedItem getFeedItem(long itemId, long feedId) {
|
|
Feed feed = getFeed(feedId);
|
|
if (feed != null && feed.getItems() != null) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getId() == itemId) {
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Get a FeedMedia object by the id of the Media object and the feed object */
|
|
public FeedMedia getFeedMedia(long id, Feed feed) {
|
|
if (feed != null) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getMedia() != null && item.getMedia().getId() == id) {
|
|
return item.getMedia();
|
|
}
|
|
}
|
|
}
|
|
Log.e(TAG, "Couldn't find FeedMedia with id " + id);
|
|
if (feed == null)
|
|
Log.e(TAG, "Feed was null");
|
|
return null;
|
|
}
|
|
|
|
/** Get a FeedMedia object by the id of the Media object. */
|
|
public FeedMedia getFeedMedia(long id) {
|
|
for (Feed feed : feeds) {
|
|
for (FeedItem item : feed.getItems()) {
|
|
if (item.getMedia() != null && item.getMedia().getId() == id) {
|
|
return item.getMedia();
|
|
}
|
|
}
|
|
}
|
|
Log.w(TAG, "Couldn't find FeedMedia with id " + id);
|
|
return null;
|
|
}
|
|
|
|
public DownloadStatus getDownloadStatus(FeedFile feedFile) {
|
|
for (DownloadStatus status : downloadLog) {
|
|
if (status.getFeedFile() == feedFile) {
|
|
return status;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Reads the database */
|
|
public void loadDBData(Context context) {
|
|
updateArrays(context);
|
|
}
|
|
|
|
public void updateArrays(Context context) {
|
|
feeds.clear();
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
extractFeedlistFromCursor(context, adapter);
|
|
extractDownloadLogFromCursor(context, adapter);
|
|
extractQueueFromCursor(context, adapter);
|
|
adapter.close();
|
|
Collections.sort(feeds, new FeedtitleComparator());
|
|
Collections.sort(unreadItems, new FeedItemPubdateComparator());
|
|
cleanupPlaybackHistory();
|
|
}
|
|
|
|
private void extractFeedlistFromCursor(Context context, PodDBAdapter adapter) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Extracting Feedlist");
|
|
Cursor feedlistCursor = adapter.getAllFeedsCursor();
|
|
if (feedlistCursor.moveToFirst()) {
|
|
do {
|
|
Date lastUpdate = new Date(
|
|
feedlistCursor
|
|
.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
|
|
Feed feed = new Feed(lastUpdate);
|
|
|
|
feed.id = feedlistCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
|
|
feed.setTitle(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_TITLE_INDEX));
|
|
feed.setLink(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_LINK_INDEX));
|
|
feed.setDescription(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_DESCRIPTION_INDEX));
|
|
feed.setPaymentLink(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX));
|
|
feed.setAuthor(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_AUTHOR_INDEX));
|
|
feed.setLanguage(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_LANGUAGE_INDEX));
|
|
feed.setType(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_TYPE_INDEX));
|
|
feed.setFeedIdentifier(feedlistCursor
|
|
.getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX));
|
|
long imageIndex = feedlistCursor
|
|
.getLong(PodDBAdapter.KEY_IMAGE_INDEX);
|
|
if (imageIndex != 0) {
|
|
feed.setImage(adapter.getFeedImage(imageIndex));
|
|
feed.getImage().setFeed(feed);
|
|
}
|
|
feed.file_url = feedlistCursor
|
|
.getString(PodDBAdapter.KEY_FILE_URL_INDEX);
|
|
feed.download_url = feedlistCursor
|
|
.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX);
|
|
feed.setDownloaded(feedlistCursor
|
|
.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
|
|
// Get FeedItem-Object
|
|
Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
|
|
feed.setItems(extractFeedItemsFromCursor(context, feed,
|
|
itemlistCursor, adapter));
|
|
itemlistCursor.close();
|
|
|
|
feeds.add(feed);
|
|
} while (feedlistCursor.moveToNext());
|
|
}
|
|
feedlistCursor.close();
|
|
|
|
}
|
|
|
|
private ArrayList<FeedItem> extractFeedItemsFromCursor(Context context,
|
|
Feed feed, Cursor itemlistCursor, PodDBAdapter adapter) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
|
|
ArrayList<FeedItem> items = new ArrayList<FeedItem>();
|
|
ArrayList<String> mediaIds = new ArrayList<String>();
|
|
|
|
if (itemlistCursor.moveToFirst()) {
|
|
do {
|
|
FeedItem item = new FeedItem();
|
|
|
|
item.id = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID);
|
|
item.setFeed(feed);
|
|
item.setTitle(itemlistCursor
|
|
.getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
|
|
item.setLink(itemlistCursor
|
|
.getString(PodDBAdapter.IDX_FI_SMALL_LINK));
|
|
item.setPubDate(new Date(itemlistCursor
|
|
.getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
|
|
item.setPaymentLink(itemlistCursor
|
|
.getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
|
|
long mediaId = itemlistCursor
|
|
.getLong(PodDBAdapter.IDX_FI_SMALL_MEDIA);
|
|
if (mediaId != 0) {
|
|
mediaIds.add(String.valueOf(mediaId));
|
|
item.setMedia(new FeedMedia(mediaId, item));
|
|
}
|
|
item.setRead((itemlistCursor
|
|
.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true
|
|
: false);
|
|
item.setItemIdentifier(itemlistCursor
|
|
.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
|
|
if (item.getState() == FeedItem.State.NEW) {
|
|
unreadItems.add(item);
|
|
}
|
|
|
|
// extract chapters
|
|
boolean hasSimpleChapters = itemlistCursor
|
|
.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
|
|
if (hasSimpleChapters) {
|
|
Cursor chapterCursor = adapter
|
|
.getSimpleChaptersOfFeedItemCursor(item);
|
|
if (chapterCursor.moveToFirst()) {
|
|
item.setChapters(new ArrayList<Chapter>());
|
|
do {
|
|
int chapterType = chapterCursor
|
|
.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
|
|
Chapter chapter = null;
|
|
long start = chapterCursor
|
|
.getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
|
|
String title = chapterCursor
|
|
.getString(PodDBAdapter.KEY_TITLE_INDEX);
|
|
String link = chapterCursor
|
|
.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
|
|
|
|
switch (chapterType) {
|
|
case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
|
|
chapter = new SimpleChapter(start, title, item,
|
|
link);
|
|
break;
|
|
case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
|
|
chapter = new ID3Chapter(start, title, item,
|
|
link);
|
|
break;
|
|
case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
|
|
chapter = new VorbisCommentChapter(start,
|
|
title, item, link);
|
|
break;
|
|
}
|
|
chapter.setId(chapterCursor
|
|
.getLong(PodDBAdapter.KEY_ID_INDEX));
|
|
item.getChapters().add(chapter);
|
|
} while (chapterCursor.moveToNext());
|
|
}
|
|
chapterCursor.close();
|
|
}
|
|
items.add(item);
|
|
} while (itemlistCursor.moveToNext());
|
|
}
|
|
extractMediafromFeedItemlist(adapter, items, mediaIds);
|
|
Collections.sort(items, new FeedItemPubdateComparator());
|
|
return items;
|
|
}
|
|
|
|
private void extractMediafromFeedItemlist(PodDBAdapter adapter,
|
|
ArrayList<FeedItem> items, ArrayList<String> mediaIds) {
|
|
ArrayList<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
|
|
Cursor cursor = adapter.getFeedMediaCursor(mediaIds
|
|
.toArray(new String[mediaIds.size()]));
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
|
|
// find matching feed item
|
|
FeedItem item = getMatchingItemForMedia(mediaId, itemsCopy);
|
|
itemsCopy.remove(item);
|
|
if (item != null) {
|
|
Date playbackCompletionDate = null;
|
|
long playbackCompletionTime = cursor
|
|
.getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
|
|
if (playbackCompletionTime > 0) {
|
|
playbackCompletionDate = new Date(
|
|
playbackCompletionTime);
|
|
}
|
|
|
|
item.setMedia(new FeedMedia(
|
|
mediaId,
|
|
item,
|
|
cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
|
|
cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
|
|
cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
|
|
cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
|
|
cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
|
|
cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
|
|
cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
|
|
playbackCompletionDate));
|
|
if (playbackCompletionDate != null) {
|
|
playbackHistory.add(item);
|
|
}
|
|
|
|
}
|
|
} while (cursor.moveToNext());
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
private FeedItem getMatchingItemForMedia(long mediaId,
|
|
ArrayList<FeedItem> items) {
|
|
for (FeedItem item : items) {
|
|
if (item.getMedia() != null && item.getMedia().getId() == mediaId) {
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void extractDownloadLogFromCursor(Context context,
|
|
PodDBAdapter adapter) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Extracting DownloadLog");
|
|
Cursor logCursor = adapter.getDownloadLogCursor();
|
|
if (logCursor.moveToFirst()) {
|
|
do {
|
|
long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
|
|
FeedFile feedfile = null;
|
|
|
|
long feedfileId = logCursor
|
|
.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
|
|
int feedfileType = logCursor
|
|
.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
|
|
if (feedfileId != 0) {
|
|
switch (feedfileType) {
|
|
case Feed.FEEDFILETYPE_FEED:
|
|
feedfile = getFeed(feedfileId);
|
|
break;
|
|
case FeedImage.FEEDFILETYPE_FEEDIMAGE:
|
|
feedfile = getFeedImage(feedfileId);
|
|
break;
|
|
case FeedMedia.FEEDFILETYPE_FEEDMEDIA:
|
|
feedfile = getFeedMedia(feedfileId);
|
|
}
|
|
}
|
|
boolean successful = logCursor
|
|
.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
|
|
int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
|
|
String reasonDetailed = logCursor
|
|
.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
|
|
String title = logCursor
|
|
.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
|
|
Date completionDate = new Date(
|
|
logCursor
|
|
.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
|
|
downloadLog.add(new DownloadStatus(id, title, feedfile,
|
|
feedfileType, successful, reason, completionDate,
|
|
reasonDetailed));
|
|
|
|
} while (logCursor.moveToNext());
|
|
}
|
|
logCursor.close();
|
|
Collections.sort(downloadLog, new DownloadStatusComparator());
|
|
}
|
|
|
|
private void extractQueueFromCursor(Context context, PodDBAdapter adapter) {
|
|
if (AppConfig.DEBUG)
|
|
Log.d(TAG, "Extracting Queue");
|
|
Cursor cursor = adapter.getQueueCursor();
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
int index = cursor.getInt(PodDBAdapter.KEY_ID_INDEX);
|
|
Feed feed = getFeed(cursor
|
|
.getLong(PodDBAdapter.KEY_QUEUE_FEED_INDEX));
|
|
if (feed != null) {
|
|
FeedItem item = getFeedItem(
|
|
cursor.getLong(PodDBAdapter.KEY_FEEDITEM_INDEX),
|
|
feed);
|
|
if (item != null) {
|
|
queue.add(index, item);
|
|
}
|
|
}
|
|
|
|
} while (cursor.moveToNext());
|
|
}
|
|
cursor.close();
|
|
}
|
|
|
|
/**
|
|
* Loads description and contentEncoded values from the database and caches
|
|
* it in the feeditem. The task callback will contain a String-array with
|
|
* the description at index 0 and the value of contentEncoded at index 1.
|
|
*/
|
|
public void loadExtraInformationOfItem(final Context context,
|
|
final FeedItem item, FeedManager.TaskCallback<String[]> callback) {
|
|
if (AppConfig.DEBUG) {
|
|
Log.d(TAG,
|
|
"Loading extra information of item with id " + item.getId());
|
|
if (item.getTitle() != null) {
|
|
Log.d(TAG, "Title: " + item.getTitle());
|
|
}
|
|
}
|
|
dbExec.execute(new FeedManager.Task<String[]>(new Handler(), callback) {
|
|
|
|
@Override
|
|
public void execute() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
Cursor extraCursor = adapter.getExtraInformationOfItem(item);
|
|
if (extraCursor.moveToFirst()) {
|
|
String description = extraCursor
|
|
.getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
|
|
String contentEncoded = extraCursor
|
|
.getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
|
|
item.setCachedDescription(description);
|
|
item.setCachedContentEncoded(contentEncoded);
|
|
setResult(new String[] { description, contentEncoded });
|
|
}
|
|
adapter.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
public void searchFeedItemDescription(final Context context,
|
|
final Feed feed, final String query,
|
|
FeedManager.QueryTaskCallback callback) {
|
|
dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
|
|
callback) {
|
|
|
|
@Override
|
|
public void execute(PodDBAdapter adapter) {
|
|
Cursor searchResult = adapter.searchItemDescriptions(feed,
|
|
query);
|
|
setResult(searchResult);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void searchFeedItemContentEncoded(final Context context,
|
|
final Feed feed, final String query,
|
|
FeedManager.QueryTaskCallback callback) {
|
|
dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
|
|
callback) {
|
|
|
|
@Override
|
|
public void execute(PodDBAdapter adapter) {
|
|
Cursor searchResult = adapter.searchItemContentEncoded(feed,
|
|
query);
|
|
setResult(searchResult);
|
|
}
|
|
});
|
|
}
|
|
|
|
public List<Feed> getFeeds() {
|
|
return feeds;
|
|
}
|
|
|
|
public List<FeedItem> getUnreadItems() {
|
|
return unreadItems;
|
|
}
|
|
|
|
public ArrayList<DownloadStatus> getDownloadLog() {
|
|
return downloadLog;
|
|
}
|
|
|
|
public List<FeedItem> getQueue() {
|
|
return queue;
|
|
}
|
|
|
|
public List<FeedItem> getPlaybackHistory() {
|
|
return playbackHistory;
|
|
}
|
|
|
|
/** Is called by a FeedManagerTask after completion. */
|
|
public interface TaskCallback<V> {
|
|
void onCompletion(V result);
|
|
}
|
|
|
|
/** Is called by a FeedManager.QueryTask after completion. */
|
|
public interface QueryTaskCallback {
|
|
void handleResult(Cursor result);
|
|
|
|
void onCompletion();
|
|
}
|
|
|
|
/** A runnable that can post a callback to a handler after completion. */
|
|
abstract class Task<V> implements Runnable {
|
|
private Handler handler;
|
|
private TaskCallback<V> callback;
|
|
private V result;
|
|
|
|
/**
|
|
* Standard contructor. No callbacks are going to be posted to a
|
|
* handler.
|
|
*/
|
|
public Task() {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* The Task will post a Runnable to 'handler' that will execute the
|
|
* 'callback' after completion.
|
|
*/
|
|
public Task(Handler handler, TaskCallback<V> callback) {
|
|
super();
|
|
this.handler = handler;
|
|
this.callback = callback;
|
|
}
|
|
|
|
@Override
|
|
public final void run() {
|
|
execute();
|
|
if (handler != null && callback != null) {
|
|
handler.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
callback.onCompletion(result);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/** This method will be executed in the same thread as the run() method. */
|
|
public abstract void execute();
|
|
|
|
public void setResult(V result) {
|
|
this.result = result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A runnable which should be used for database queries. The onCompletion
|
|
* method is executed on the database executor to handle Cursors correctly.
|
|
* This class automatically creates a PodDBAdapter object and closes it when
|
|
* it is no longer in use.
|
|
*/
|
|
abstract class QueryTask implements Runnable {
|
|
private QueryTaskCallback callback;
|
|
private Cursor result;
|
|
private Context context;
|
|
private Handler handler;
|
|
|
|
public QueryTask(Context context, Handler handler,
|
|
QueryTaskCallback callback) {
|
|
this.callback = callback;
|
|
this.context = context;
|
|
this.handler = handler;
|
|
}
|
|
|
|
@Override
|
|
public final void run() {
|
|
PodDBAdapter adapter = new PodDBAdapter(context);
|
|
adapter.open();
|
|
execute(adapter);
|
|
callback.handleResult(result);
|
|
if (result != null && !result.isClosed()) {
|
|
result.close();
|
|
}
|
|
adapter.close();
|
|
if (handler != null && callback != null) {
|
|
handler.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
callback.onCompletion();
|
|
}
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
public abstract void execute(PodDBAdapter adapter);
|
|
|
|
protected void setResult(Cursor c) {
|
|
result = c;
|
|
}
|
|
}
|
|
|
|
}
|