Search in sources :

Example 1 with EpisodeAction

use of de.danoeh.antennapod.net.sync.model.EpisodeAction in project AntennaPod by AntennaPod.

the class ResponseMapper method readEpisodeActionsFromJsonObject.

public static EpisodeActionChanges readEpisodeActionsFromJsonObject(@NonNull JSONObject object) throws JSONException {
    List<EpisodeAction> episodeActions = new ArrayList<>();
    long timestamp = object.getLong("timestamp");
    JSONArray jsonActions = object.getJSONArray("actions");
    for (int i = 0; i < jsonActions.length(); i++) {
        JSONObject jsonAction = jsonActions.getJSONObject(i);
        EpisodeAction episodeAction = EpisodeAction.readFromJsonObject(jsonAction);
        if (episodeAction != null) {
            episodeActions.add(episodeAction);
        }
    }
    return new EpisodeActionChanges(episodeActions, timestamp);
}
Also used : JSONObject(org.json.JSONObject) ArrayList(java.util.ArrayList) JSONArray(org.json.JSONArray) EpisodeAction(de.danoeh.antennapod.net.sync.model.EpisodeAction) EpisodeActionChanges(de.danoeh.antennapod.net.sync.model.EpisodeActionChanges)

Example 2 with EpisodeAction

use of de.danoeh.antennapod.net.sync.model.EpisodeAction in project AntennaPod by AntennaPod.

the class NextcloudSyncService method uploadEpisodeActionsPartial.

private void uploadEpisodeActionsPartial(List<EpisodeAction> queuedEpisodeActions, int from, int to) throws NextcloudSynchronizationServiceException {
    try {
        final JSONArray list = new JSONArray();
        for (int i = from; i < to; i++) {
            EpisodeAction episodeAction = queuedEpisodeActions.get(i);
            JSONObject obj = episodeAction.writeToJsonObject();
            if (obj != null) {
                list.put(obj);
            }
        }
        HttpUrl.Builder url = makeUrl("/index.php/apps/gpoddersync/episode_action/create");
        RequestBody requestBody = RequestBody.create(MediaType.get("application/json"), list.toString());
        performRequest(url, "POST", requestBody);
    } catch (Exception e) {
        e.printStackTrace();
        throw new NextcloudSynchronizationServiceException(e);
    }
}
Also used : JSONObject(org.json.JSONObject) JSONArray(org.json.JSONArray) EpisodeAction(de.danoeh.antennapod.net.sync.model.EpisodeAction) HttpUrl(okhttp3.HttpUrl) SyncServiceException(de.danoeh.antennapod.net.sync.model.SyncServiceException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) JSONException(org.json.JSONException) RequestBody(okhttp3.RequestBody)

Example 3 with EpisodeAction

use of de.danoeh.antennapod.net.sync.model.EpisodeAction in project AntennaPod by AntennaPod.

the class FeedItemMenuHandler method onMenuItemClicked.

/**
 * Default menu handling for the given FeedItem.
 *
 * A Fragment instance, (rather than the more generic Context), is needed as a parameter
 * to support some UI operations, e.g., creating a Snackbar.
 */
public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId, @NonNull FeedItem selectedItem) {
    @NonNull Context context = fragment.requireContext();
    if (menuItemId == R.id.skip_episode_item) {
        IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
    } else if (menuItemId == R.id.remove_item) {
        DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
    } else if (menuItemId == R.id.remove_new_flag_item) {
        removeNewFlagWithUndo(fragment, selectedItem);
    } else if (menuItemId == R.id.mark_read_item) {
        selectedItem.setPlayed(true);
        DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true);
        if (SynchronizationSettings.isProviderConnected()) {
            FeedMedia media = selectedItem.getMedia();
            // not all items have media, Gpodder only cares about those that do
            if (media != null) {
                EpisodeAction actionPlay = new EpisodeAction.Builder(selectedItem, EpisodeAction.PLAY).currentTimestamp().started(media.getDuration() / 1000).position(media.getDuration() / 1000).total(media.getDuration() / 1000).build();
                SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionPlay);
            }
        }
    } else if (menuItemId == R.id.mark_unread_item) {
        selectedItem.setPlayed(false);
        DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
        if (selectedItem.getMedia() != null) {
            EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW).currentTimestamp().build();
            SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionNew);
        }
    } else if (menuItemId == R.id.add_to_queue_item) {
        DBWriter.addQueueItem(context, selectedItem);
    } else if (menuItemId == R.id.remove_from_queue_item) {
        DBWriter.removeQueueItem(context, true, selectedItem);
    } else if (menuItemId == R.id.add_to_favorites_item) {
        DBWriter.addFavoriteItem(selectedItem);
    } else if (menuItemId == R.id.remove_from_favorites_item) {
        DBWriter.removeFavoriteItem(selectedItem);
    } else if (menuItemId == R.id.reset_position) {
        selectedItem.getMedia().setPosition(0);
        if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == selectedItem.getMedia().getId()) {
            PlaybackPreferences.writeNoMediaPlaying();
            IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
        }
        DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, true);
    } else if (menuItemId == R.id.visit_website_item) {
        IntentUtils.openInBrowser(context, FeedItemUtil.getLinkWithFallback(selectedItem));
    } else if (menuItemId == R.id.share_item) {
        ShareDialog shareDialog = ShareDialog.newInstance(selectedItem);
        shareDialog.show((fragment.getActivity().getSupportFragmentManager()), "ShareEpisodeDialog");
    } else {
        Log.d(TAG, "Unknown menuItemId: " + menuItemId);
        return false;
    }
    return true;
}
Also used : Context(android.content.Context) ShareDialog(de.danoeh.antennapod.dialog.ShareDialog) FeedMedia(de.danoeh.antennapod.model.feed.FeedMedia) NonNull(androidx.annotation.NonNull) EpisodeAction(de.danoeh.antennapod.net.sync.model.EpisodeAction)

Example 4 with EpisodeAction

use of de.danoeh.antennapod.net.sync.model.EpisodeAction in project AntennaPod by AntennaPod.

the class DBTasks method updateFeed.

/**
 * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same
 * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
 * These FeedItems will be marked as unread with the exception of the most recent FeedItem.
 * <p/>
 * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior.
 * <p/>
 * This method should NOT be executed on the GUI thread.
 *
 * @param context Used for accessing the DB.
 * @param newFeed The new Feed object.
 * @param removeUnlistedItems The item list in the new Feed object is considered to be exhaustive.
 *                            I.e. items are removed from the database if they are not in this item list.
 * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise.
 */
public static synchronized Feed updateFeed(Context context, Feed newFeed, boolean removeUnlistedItems) {
    Feed resultFeed;
    List<FeedItem> unlistedItems = new ArrayList<>();
    PodDBAdapter adapter = PodDBAdapter.getInstance();
    adapter.open();
    // Look up feed in the feedslist
    final Feed savedFeed = searchFeedByIdentifyingValueOrID(adapter, newFeed);
    if (savedFeed == null) {
        Log.d(TAG, "Found no existing Feed with title " + newFeed.getTitle() + ". Adding as new one.");
        // Add a new Feed
        // all new feeds will have the most recent item marked as unplayed
        FeedItem mostRecent = newFeed.getMostRecentItem();
        if (mostRecent != null) {
            mostRecent.setNew();
        }
        resultFeed = newFeed;
    } else {
        Log.d(TAG, "Feed with title " + newFeed.getTitle() + " already exists. Syncing new with existing one.");
        Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
        if (newFeed.getPageNr() == savedFeed.getPageNr()) {
            if (savedFeed.compareWithOther(newFeed)) {
                Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes");
                savedFeed.updateFromOther(newFeed);
            }
        } else {
            Log.d(TAG, "New feed has a higher page number.");
            savedFeed.setNextPageLink(newFeed.getNextPageLink());
        }
        if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) {
            Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
            savedFeed.getPreferences().updateFromOther(newFeed.getPreferences());
        }
        // get the most recent date now, before we start changing the list
        FeedItem priorMostRecent = savedFeed.getMostRecentItem();
        Date priorMostRecentDate = null;
        if (priorMostRecent != null) {
            priorMostRecentDate = priorMostRecent.getPubDate();
        }
        // Look for new or updated Items
        for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
            final FeedItem item = newFeed.getItems().get(idx);
            FeedItem possibleDuplicate = searchFeedItemGuessDuplicate(newFeed.getItems(), item);
            if (!newFeed.isLocalFeed() && possibleDuplicate != null && item != possibleDuplicate) {
                // Canonical episode is the first one returned (usually oldest)
                DBWriter.addDownloadStatus(new DownloadStatus(savedFeed, item.getTitle(), DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false, "The podcast host appears to have added the same episode twice. " + "AntennaPod still refreshed the feed and attempted to repair it." + "\n\nOriginal episode:\n" + duplicateEpisodeDetails(item) + "\n\nSecond episode that is also in the feed:\n" + duplicateEpisodeDetails(possibleDuplicate), false));
                continue;
            }
            FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed.getItems(), item);
            if (!newFeed.isLocalFeed() && oldItem == null) {
                oldItem = searchFeedItemGuessDuplicate(savedFeed.getItems(), item);
                if (oldItem != null) {
                    Log.d(TAG, "Repaired duplicate: " + oldItem + ", " + item);
                    DBWriter.addDownloadStatus(new DownloadStatus(savedFeed, item.getTitle(), DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false, "The podcast host changed the ID of an existing episode instead of just " + "updating the episode itself. AntennaPod still refreshed the feed and " + "attempted to repair it." + "\n\nOriginal episode:\n" + duplicateEpisodeDetails(oldItem) + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item), false));
                    oldItem.setItemIdentifier(item.getItemIdentifier());
                    if (oldItem.isPlayed() && oldItem.getMedia() != null) {
                        EpisodeAction action = new EpisodeAction.Builder(oldItem, EpisodeAction.PLAY).currentTimestamp().started(oldItem.getMedia().getDuration() / 1000).position(oldItem.getMedia().getDuration() / 1000).total(oldItem.getMedia().getDuration() / 1000).build();
                        SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
                    }
                }
            }
            if (oldItem != null) {
                oldItem.updateFromOther(item);
            } else {
                // item is new
                item.setFeed(savedFeed);
                if (idx >= savedFeed.getItems().size()) {
                    savedFeed.getItems().add(item);
                } else {
                    savedFeed.getItems().add(idx, item);
                }
                // New items that do not have a pubDate set are always marked as new
                if (item.getPubDate() == null || priorMostRecentDate == null || priorMostRecentDate.before(item.getPubDate()) || priorMostRecentDate.equals(item.getPubDate())) {
                    Log.d(TAG, "Marking item published on " + item.getPubDate() + " new, prior most recent date = " + priorMostRecentDate);
                    item.setNew();
                }
            }
        }
        // identify items to be removed
        if (removeUnlistedItems) {
            Iterator<FeedItem> it = savedFeed.getItems().iterator();
            while (it.hasNext()) {
                FeedItem feedItem = it.next();
                if (searchFeedItemByIdentifyingValue(newFeed.getItems(), feedItem) == null) {
                    unlistedItems.add(feedItem);
                    it.remove();
                }
            }
        }
        // update attributes
        savedFeed.setLastUpdate(newFeed.getLastUpdate());
        savedFeed.setType(newFeed.getType());
        savedFeed.setLastUpdateFailed(false);
        resultFeed = savedFeed;
    }
    try {
        if (savedFeed == null) {
            DBWriter.addNewFeed(context, newFeed).get();
            // Update with default values that are set in database
            resultFeed = searchFeedByIdentifyingValueOrID(adapter, newFeed);
        } else {
            DBWriter.setCompleteFeed(savedFeed).get();
        }
        if (removeUnlistedItems) {
            DBWriter.deleteFeedItems(context, unlistedItems).get();
        }
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    adapter.close();
    if (savedFeed != null) {
        EventBus.getDefault().post(new FeedListUpdateEvent(savedFeed));
    } else {
        EventBus.getDefault().post(new FeedListUpdateEvent(Collections.emptyList()));
    }
    return resultFeed;
}
Also used : ArrayList(java.util.ArrayList) Date(java.util.Date) EpisodeAction(de.danoeh.antennapod.net.sync.model.EpisodeAction) FeedItemPubdateComparator(de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator) FeedItem(de.danoeh.antennapod.model.feed.FeedItem) DownloadStatus(de.danoeh.antennapod.core.service.download.DownloadStatus) ExecutionException(java.util.concurrent.ExecutionException) FeedListUpdateEvent(de.danoeh.antennapod.event.FeedListUpdateEvent) Feed(de.danoeh.antennapod.model.feed.Feed)

Example 5 with EpisodeAction

use of de.danoeh.antennapod.net.sync.model.EpisodeAction in project AntennaPod by AntennaPod.

the class EpisodeActionFilter method getRemoteActionsOverridingLocalActions.

public static Map<Pair<String, String>, EpisodeAction> getRemoteActionsOverridingLocalActions(List<EpisodeAction> remoteActions, List<EpisodeAction> queuedEpisodeActions) {
    // make sure more recent local actions are not overwritten by older remote actions
    Map<Pair<String, String>, EpisodeAction> remoteActionsThatOverrideLocalActions = new ArrayMap<>();
    Map<Pair<String, String>, EpisodeAction> localMostRecentPlayActions = createUniqueLocalMostRecentPlayActions(queuedEpisodeActions);
    for (EpisodeAction remoteAction : remoteActions) {
        Pair<String, String> key = new Pair<>(remoteAction.getPodcast(), remoteAction.getEpisode());
        switch(remoteAction.getAction()) {
            case NEW:
                remoteActionsThatOverrideLocalActions.put(key, remoteAction);
                break;
            case DOWNLOAD:
                break;
            case PLAY:
                EpisodeAction localMostRecent = localMostRecentPlayActions.get(key);
                if (secondActionOverridesFirstAction(remoteAction, localMostRecent)) {
                    break;
                }
                EpisodeAction remoteMostRecentAction = remoteActionsThatOverrideLocalActions.get(key);
                if (secondActionOverridesFirstAction(remoteAction, remoteMostRecentAction)) {
                    break;
                }
                remoteActionsThatOverrideLocalActions.put(key, remoteAction);
                break;
            case DELETE:
                // NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
                break;
            default:
                Log.e(TAG, "Unknown remoteAction: " + remoteAction);
                break;
        }
    }
    return remoteActionsThatOverrideLocalActions;
}
Also used : ArrayMap(androidx.collection.ArrayMap) EpisodeAction(de.danoeh.antennapod.net.sync.model.EpisodeAction) Pair(androidx.core.util.Pair)

Aggregations

EpisodeAction (de.danoeh.antennapod.net.sync.model.EpisodeAction)17 ArrayList (java.util.ArrayList)8 Pair (androidx.core.util.Pair)7 FeedItem (de.danoeh.antennapod.model.feed.FeedItem)5 Date (java.util.Date)5 SimpleDateFormat (java.text.SimpleDateFormat)4 JSONArray (org.json.JSONArray)4 FeedMedia (de.danoeh.antennapod.model.feed.FeedMedia)3 JSONException (org.json.JSONException)3 JSONObject (org.json.JSONObject)3 DownloadStatus (de.danoeh.antennapod.core.service.download.DownloadStatus)2 EpisodeActionChanges (de.danoeh.antennapod.net.sync.model.EpisodeActionChanges)2 SyncServiceException (de.danoeh.antennapod.net.sync.model.SyncServiceException)2 File (java.io.File)2 MalformedURLException (java.net.MalformedURLException)2 ExecutionException (java.util.concurrent.ExecutionException)2 RequestBody (okhttp3.RequestBody)2 Context (android.content.Context)1 MediaMetadataRetriever (android.media.MediaMetadataRetriever)1 NonNull (androidx.annotation.NonNull)1