summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorthrillfall <thrillfall@users.noreply.github.com>2021-08-20 20:17:23 +0200
committerGitHub <noreply@github.com>2021-08-20 20:17:23 +0200
commitdb391867608fa37f6568eca02678b3b376e52fa8 (patch)
tree74210432fbb3426c3243687de580094d9fc513ff /core/src/main
parent7ebaa9f619e004c8f2231bce1d170007c9de1544 (diff)
downloadAntennaPod-db391867608fa37f6568eca02678b3b376e52fa8.zip
Identify episodes by guid (#5326)
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java79
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java102
6 files changed, 145 insertions, 88 deletions
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 dc15b8a9a..49eca1027 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
@@ -567,15 +567,16 @@ public final class DBReader {
/**
* Loads a specific FeedItem from the database.
*
- * @param podcastUrl the corresponding feed's url
+ * @param guid feed item guid
* @param episodeUrl the feed item's url
* @return The FeedItem or null if the FeedItem could not be found.
* Does NOT load additional attributes like feed or queue state.
*/
@Nullable
- private static FeedItem getFeedItemByUrl(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
- Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
- try (Cursor cursor = adapter.getFeedItemCursor(podcastUrl, episodeUrl)) {
+ private static FeedItem getFeedItemByGuidOrEpisodeUrl(final String guid, final String episodeUrl,
+ PodDBAdapter adapter) {
+ Log.d(TAG, "Loading feeditem with guid " + guid + " or episode url " + episodeUrl);
+ try (Cursor cursor = adapter.getFeedItemCursor(guid, episodeUrl)) {
if (!cursor.moveToNext()) {
return null;
}
@@ -626,18 +627,18 @@ public final class DBReader {
/**
* Loads a specific FeedItem from the database.
*
- * @param podcastUrl the corresponding feed's url
+ * @param guid feed item guid
* @param episodeUrl the feed item's url
* @return The FeedItem or null if the FeedItem could not be found.
* Does NOT load additional attributes like feed or queue state.
*/
- public static FeedItem getFeedItemByUrl(final String podcastUrl, final String episodeUrl) {
- Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
+ public static FeedItem getFeedItemByGuidOrEpisodeUrl(final String guid, final String episodeUrl) {
+ Log.d(TAG, "getFeedItem() called with: " + "guid = [" + guid + "], episodeUrl = [" + episodeUrl + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try {
- return getFeedItemByUrl(podcastUrl, episodeUrl, adapter);
+ return getFeedItemByGuidOrEpisodeUrl(guid, episodeUrl, adapter);
} finally {
adapter.close();
}
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 9e60f4a4d..34ea5e207 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
@@ -803,7 +803,7 @@ public class DBWriter {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- adapter.setFeedItemlist(items);
+ adapter.storeFeedItemlist(items);
adapter.close();
EventBus.getDefault().post(FeedItemEvent.updated(items));
});
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 21ca1043f..85ce2dc99 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
@@ -558,7 +558,7 @@ public class PodDBAdapter {
setFeed(feed);
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
- setFeedItem(item, false);
+ updateOrInsertFeedItem(item, false);
}
}
if (feed.getPreferences() != null) {
@@ -582,11 +582,11 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_DOWNLOAD_URL + "=?", new String[]{original});
}
- public void setFeedItemlist(List<FeedItem> items) {
+ public void storeFeedItemlist(List<FeedItem> items) {
try {
db.beginTransactionNonExclusive();
for (FeedItem item : items) {
- setFeedItem(item, true);
+ updateOrInsertFeedItem(item, true);
}
db.setTransactionSuccessful();
} catch (SQLException e) {
@@ -600,7 +600,7 @@ public class PodDBAdapter {
long result = 0;
try {
db.beginTransactionNonExclusive();
- result = setFeedItem(item, true);
+ result = updateOrInsertFeedItem(item, true);
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
@@ -618,7 +618,7 @@ public class PodDBAdapter {
* false if the method is executed on a list of FeedItems of the same Feed.
* @return the id of the entry
*/
- private long setFeedItem(FeedItem item, boolean saveFeed) {
+ private long updateOrInsertFeedItem(FeedItem item, boolean saveFeed) {
if (item.getId() == 0 && item.getPubDate() == null) {
Log.e(TAG, "Newly saved item has no pubDate. Using current date as pubDate");
item.setPubDate(new Date());
@@ -1110,14 +1110,19 @@ public class PodDBAdapter {
return db.rawQuery(query, null);
}
- public final Cursor getFeedItemCursor(final String podcastUrl, final String episodeUrl) {
- String escapedPodcastUrl = DatabaseUtils.sqlEscapeString(podcastUrl);
+ public final Cursor getFeedItemCursor(final String guid, final String episodeUrl) {
String escapedEpisodeUrl = DatabaseUtils.sqlEscapeString(episodeUrl);
+ String whereClauseCondition = TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOAD_URL + "=" + escapedEpisodeUrl;
+
+ if (guid != null) {
+ String escapedGuid = DatabaseUtils.sqlEscapeString(guid);
+ whereClauseCondition = TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + "=" + escapedGuid;
+ }
+
final String query = SELECT_FEED_ITEMS_AND_MEDIA
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOAD_URL + "=" + escapedEpisodeUrl
- + " AND " + TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + "=" + escapedPodcastUrl;
+ + " WHERE " + whereClauseCondition;
Log.d(TAG, "SQL: " + query);
return db.rawQuery(query, null);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java b/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java
new file mode 100644
index 000000000..c74356d98
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java
@@ -0,0 +1,79 @@
+package de.danoeh.antennapod.core.sync;
+
+import android.util.Log;
+
+import androidx.collection.ArrayMap;
+import androidx.core.util.Pair;
+
+import java.util.List;
+import java.util.Map;
+
+import de.danoeh.antennapod.net.sync.model.EpisodeAction;
+
+public class EpisodeActionFilter {
+
+ public static final String TAG = "EpisodeActionFilter";
+
+ 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) {
+ Log.d(TAG, "Processing remoteAction: " + remoteAction.toString());
+ 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;
+ }
+
+ private static Map<Pair<String, String>, EpisodeAction> createUniqueLocalMostRecentPlayActions(
+ List<EpisodeAction> queuedEpisodeActions) {
+ Map<Pair<String, String>, EpisodeAction> localMostRecentPlayAction;
+ localMostRecentPlayAction = new ArrayMap<>();
+ for (EpisodeAction action : queuedEpisodeActions) {
+ Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
+ EpisodeAction mostRecent = localMostRecentPlayAction.get(key);
+ if (mostRecent == null || mostRecent.getTimestamp() == null) {
+ localMostRecentPlayAction.put(key, action);
+ } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ localMostRecentPlayAction.put(key, action);
+ }
+ }
+ return localMostRecentPlayAction;
+ }
+
+ private static boolean secondActionOverridesFirstAction(EpisodeAction firstAction,
+ EpisodeAction secondAction) {
+ return secondAction != null
+ && secondAction.getTimestamp() != null
+ && secondAction.getTimestamp().after(firstAction.getTimestamp());
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java b/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java
new file mode 100644
index 000000000..6d80a6457
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java
@@ -0,0 +1,10 @@
+package de.danoeh.antennapod.core.sync;
+
+public class GuidValidator {
+
+ public static boolean isValidGuid(String guid) {
+ return guid != null
+ && !guid.trim().isEmpty();
+ }
+}
+
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
index 4736a2c33..9803a29db 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
@@ -7,8 +7,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
+
import androidx.annotation.NonNull;
-import androidx.collection.ArrayMap;
import androidx.core.app.NotificationCompat;
import androidx.core.util.Pair;
import androidx.work.BackoffPolicy;
@@ -19,6 +19,7 @@ import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
+
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.SyncServiceEvent;
import de.danoeh.antennapod.model.feed.Feed;
@@ -45,6 +46,7 @@ import de.danoeh.antennapod.net.sync.model.SyncServiceException;
import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
+
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
@@ -112,13 +114,13 @@ public class SyncService extends Worker {
public static void clearQueue(Context context) {
executeLockedAsync(() ->
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
- .putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0)
- .putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0)
- .putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0)
- .putString(PREF_QUEUED_EPISODE_ACTIONS, "[]")
- .putString(PREF_QUEUED_FEEDS_ADDED, "[]")
- .putString(PREF_QUEUED_FEEDS_REMOVED, "[]")
- .apply());
+ .putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0)
+ .putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0)
+ .putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0)
+ .putString(PREF_QUEUED_EPISODE_ACTIONS, "[]")
+ .putString(PREF_QUEUED_FEEDS_ADDED, "[]")
+ .putString(PREF_QUEUED_FEEDS_REMOVED, "[]")
+ .apply());
}
public static void enqueueFeedAdded(Context context, String downloadUrl) {
@@ -258,7 +260,7 @@ public class SyncService extends Worker {
lock.unlock();
}
}).subscribeOn(Schedulers.io())
- .subscribe();
+ .subscribe();
}
}
@@ -429,71 +431,31 @@ public class SyncService extends Worker {
return;
}
- Map<Pair<String, String>, EpisodeAction> localMostRecentPlayAction = new ArrayMap<>();
- for (EpisodeAction action : getQueuedEpisodeActions()) {
- Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
- EpisodeAction mostRecent = localMostRecentPlayAction.get(key);
- if (mostRecent == null || mostRecent.getTimestamp() == null) {
- localMostRecentPlayAction.put(key, action);
- } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
- localMostRecentPlayAction.put(key, action);
- }
- }
-
- // make sure more recent local actions are not overwritten by older remote actions
- Map<Pair<String, String>, EpisodeAction> mostRecentPlayAction = new ArrayMap<>();
- for (EpisodeAction action : remoteActions) {
- Log.d(TAG, "Processing action: " + action.toString());
- switch (action.getAction()) {
- case NEW:
- FeedItem newItem = DBReader.getFeedItemByUrl(action.getPodcast(), action.getEpisode());
- if (newItem != null) {
- DBWriter.markItemPlayed(newItem, FeedItem.UNPLAYED, true);
- } else {
- Log.i(TAG, "Unknown feed item: " + action);
- }
- break;
- case DOWNLOAD:
- break;
- case PLAY:
- Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
- EpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
- if (localMostRecent == null || localMostRecent.getTimestamp() == null
- || localMostRecent.getTimestamp().before(action.getTimestamp())) {
- EpisodeAction mostRecent = mostRecentPlayAction.get(key);
- if (mostRecent == null || mostRecent.getTimestamp() == null) {
- mostRecentPlayAction.put(key, action);
- } else if (action.getTimestamp() != null
- && mostRecent.getTimestamp().before(action.getTimestamp())) {
- mostRecentPlayAction.put(key, action);
- } else {
- Log.d(TAG, "No date information in action, skipping it");
- }
- }
- break;
- case DELETE:
- // NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
- break;
- default:
- Log.e(TAG, "Unknown action: " + action);
- break;
- }
- }
+ Map<Pair<String, String>, EpisodeAction> playActionsToUpdate = EpisodeActionFilter
+ .getRemoteActionsOverridingLocalActions(remoteActions, getQueuedEpisodeActions());
LongList queueToBeRemoved = new LongList();
List<FeedItem> updatedItems = new ArrayList<>();
- for (EpisodeAction action : mostRecentPlayAction.values()) {
- FeedItem playItem = DBReader.getFeedItemByUrl(action.getPodcast(), action.getEpisode());
+ for (EpisodeAction action : playActionsToUpdate.values()) {
+ String guid = GuidValidator.isValidGuid(action.getGuid()) ? action.getGuid() : null;
+ FeedItem feedItem = DBReader.getFeedItemByGuidOrEpisodeUrl(guid, action.getEpisode());
+ if (feedItem == null) {
+ Log.i(TAG, "Unknown feed item: " + action);
+ continue;
+ }
+ if (action.getAction() == EpisodeAction.NEW) {
+ DBWriter.markItemPlayed(feedItem, FeedItem.UNPLAYED, true);
+ continue;
+ }
Log.d(TAG, "Most recent play action: " + action.toString());
- if (playItem != null) {
- FeedMedia media = playItem.getMedia();
- media.setPosition(action.getPosition() * 1000);
- if (FeedItemUtil.hasAlmostEnded(playItem.getMedia())) {
- Log.d(TAG, "Marking as played");
- playItem.setPlayed(true);
- queueToBeRemoved.add(playItem.getId());
- }
- updatedItems.add(playItem);
+ FeedMedia media = feedItem.getMedia();
+ media.setPosition(action.getPosition() * 1000);
+ if (FeedItemUtil.hasAlmostEnded(feedItem.getMedia())) {
+ Log.d(TAG, "Marking as played");
+ feedItem.setPlayed(true);
+ queueToBeRemoved.add(feedItem.getId());
}
+ updatedItems.add(feedItem);
+
}
DBWriter.removeQueueItem(getApplicationContext(), false, queueToBeRemoved.toArray());
DBReader.loadAdditionalFeedItemListData(updatedItems);