Identify episodes by guid (#5326)
This commit is contained in:
parent
7ebaa9f619
commit
db39186760
@ -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();
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
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);
|
||||
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());
|
||||
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);
|
||||
|
@ -192,7 +192,7 @@ public class CastUtils {
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
FeedItem feedItem = DBReader.getFeedItemByUrl(metadata.getString(KEY_FEED_URL),
|
||||
FeedItem feedItem = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
|
||||
metadata.getString(KEY_EPISODE_IDENTIFIER));
|
||||
if (feedItem != null) {
|
||||
result = feedItem.getMedia();
|
||||
|
@ -218,7 +218,7 @@ public class DbReaderTest {
|
||||
}
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.setFeedItemlist(downloaded);
|
||||
adapter.storeFeedItemlist(downloaded);
|
||||
adapter.close();
|
||||
return downloaded;
|
||||
}
|
||||
@ -257,7 +257,7 @@ public class DbReaderTest {
|
||||
}
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.setFeedItemlist(newItems);
|
||||
adapter.storeFeedItemlist(newItems);
|
||||
adapter.close();
|
||||
return newItems;
|
||||
}
|
||||
@ -417,4 +417,23 @@ public class DbReaderTest {
|
||||
assertTrue(item2.hasChapters());
|
||||
assertEquals(item1.getChapters(), item2.getChapters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemByEpisodeUrl() {
|
||||
List<Feed> feeds = saveFeedlist(1, 1, true);
|
||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||
FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
|
||||
item1.getMedia().getDownload_url());
|
||||
assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemByGuid() {
|
||||
List<Feed> feeds = saveFeedlist(1, 1, true);
|
||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||
|
||||
FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
|
||||
item1.getMedia().getDownload_url());
|
||||
assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,187 @@
|
||||
package de.danoeh.antennapod.core.sync;
|
||||
|
||||
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
|
||||
|
||||
|
||||
public class EpisodeActionFilterTest extends TestCase {
|
||||
|
||||
EpisodeActionFilter episodeActionFilter = new EpisodeActionFilter();
|
||||
|
||||
public void testGetRemoteActionsHappeningAfterLocalActions() throws ParseException {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date morning = format.parse("2021-01-01 08:00:00");
|
||||
Date lateMorning = format.parse("2021-01-01 09:00:00");
|
||||
|
||||
List<EpisodeAction> episodeActions = new ArrayList<>();
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(lateMorning)
|
||||
.position(20)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Date morningFiveMinutesLater = format.parse("2021-01-01 08:05:00");
|
||||
List<EpisodeAction> remoteActions = new ArrayList<>();
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesLater)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesLater)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
|
||||
.getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
|
||||
assertSame(1, uniqueList.size());
|
||||
}
|
||||
|
||||
public void testGetRemoteActionsHappeningBeforeLocalActions() throws ParseException {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date morning = format.parse("2021-01-01 08:00:00");
|
||||
Date lateMorning = format.parse("2021-01-01 09:00:00");
|
||||
|
||||
List<EpisodeAction> episodeActions = new ArrayList<>();
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(lateMorning)
|
||||
.position(20)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Date morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00");
|
||||
List<EpisodeAction> remoteActions = new ArrayList<>();
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesEarlier)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesEarlier)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
|
||||
.getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
|
||||
assertSame(0, uniqueList.size());
|
||||
}
|
||||
|
||||
public void testGetMultipleRemoteActionsHappeningAfterLocalActions() throws ParseException {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date morning = format.parse("2021-01-01 08:00:00");
|
||||
|
||||
List<EpisodeAction> episodeActions = new ArrayList<>();
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Date morningFiveMinutesLater = format.parse("2021-01-01 08:05:00");
|
||||
List<EpisodeAction> remoteActions = new ArrayList<>();
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesLater)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesLater)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
|
||||
.getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
|
||||
assertEquals(2, uniqueList.size());
|
||||
}
|
||||
|
||||
public void testGetMultipleRemoteActionsHappeningBeforeLocalActions() throws ParseException {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date morning = format.parse("2021-01-01 08:00:00");
|
||||
|
||||
List<EpisodeAction> episodeActions = new ArrayList<>();
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
episodeActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morning)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Date morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00");
|
||||
List<EpisodeAction> remoteActions = new ArrayList<>();
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesEarlier)
|
||||
.position(10)
|
||||
.build()
|
||||
);
|
||||
remoteActions.add(new EpisodeAction
|
||||
.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
|
||||
.timestamp(morningFiveMinutesEarlier)
|
||||
.position(5)
|
||||
.build()
|
||||
);
|
||||
|
||||
Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
|
||||
.getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
|
||||
assertEquals(0, uniqueList.size());
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package de.danoeh.antennapod.core.sync;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class GuidValidatorTest extends TestCase {
|
||||
|
||||
public void testIsValidGuid() {
|
||||
assertTrue(GuidValidator.isValidGuid("skfjsdvgsd"));
|
||||
}
|
||||
|
||||
public void testIsInvalidGuid() {
|
||||
assertFalse(GuidValidator.isValidGuid(""));
|
||||
assertFalse(GuidValidator.isValidGuid(" "));
|
||||
assertFalse(GuidValidator.isValidGuid("\n"));
|
||||
assertFalse(GuidValidator.isValidGuid(" \n"));
|
||||
assertFalse(GuidValidator.isValidGuid(null));
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ public class EpisodeAction {
|
||||
|
||||
private final String podcast;
|
||||
private final String episode;
|
||||
private final String guid;
|
||||
private final Action action;
|
||||
private final Date timestamp;
|
||||
private final int started;
|
||||
@ -34,6 +35,7 @@ public class EpisodeAction {
|
||||
private EpisodeAction(Builder builder) {
|
||||
this.podcast = builder.podcast;
|
||||
this.episode = builder.episode;
|
||||
this.guid = builder.guid;
|
||||
this.action = builder.action;
|
||||
this.timestamp = builder.timestamp;
|
||||
this.started = builder.started;
|
||||
@ -72,6 +74,10 @@ public class EpisodeAction {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
String guid = object.optString("guid", null);
|
||||
if (!TextUtils.isEmpty(guid)) {
|
||||
builder.guid(guid);
|
||||
}
|
||||
if (action == EpisodeAction.Action.PLAY) {
|
||||
int started = object.optInt("started", -1);
|
||||
int position = object.optInt("position", -1);
|
||||
@ -94,6 +100,10 @@ public class EpisodeAction {
|
||||
return this.episode;
|
||||
}
|
||||
|
||||
public String getGuid() {
|
||||
return this.guid;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return this.action;
|
||||
}
|
||||
@ -143,16 +153,21 @@ public class EpisodeAction {
|
||||
}
|
||||
|
||||
EpisodeAction that = (EpisodeAction) o;
|
||||
return started == that.started && position == that.position && total == that.total && action != that.action
|
||||
return started == that.started
|
||||
&& position == that.position
|
||||
&& total == that.total
|
||||
&& action != that.action
|
||||
&& ObjectsCompat.equals(podcast, that.podcast)
|
||||
&& ObjectsCompat.equals(episode, that.episode)
|
||||
&& ObjectsCompat.equals(timestamp, that.timestamp);
|
||||
&& ObjectsCompat.equals(timestamp, that.timestamp)
|
||||
&& ObjectsCompat.equals(guid, that.guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = podcast != null ? podcast.hashCode() : 0;
|
||||
result = 31 * result + (episode != null ? episode.hashCode() : 0);
|
||||
result = 31 * result + (guid != null ? guid.hashCode() : 0);
|
||||
result = 31 * result + (action != null ? action.hashCode() : 0);
|
||||
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
|
||||
result = 31 * result + started;
|
||||
@ -171,6 +186,7 @@ public class EpisodeAction {
|
||||
try {
|
||||
obj.putOpt("podcast", this.podcast);
|
||||
obj.putOpt("episode", this.episode);
|
||||
obj.putOpt("guid", this.guid);
|
||||
obj.put("action", this.getActionString());
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
|
||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
@ -193,6 +209,7 @@ public class EpisodeAction {
|
||||
return "EpisodeAction{"
|
||||
+ "podcast='" + podcast + '\''
|
||||
+ ", episode='" + episode + '\''
|
||||
+ ", guid='" + guid + '\''
|
||||
+ ", action=" + action
|
||||
+ ", timestamp=" + timestamp
|
||||
+ ", started=" + started
|
||||
@ -217,6 +234,7 @@ public class EpisodeAction {
|
||||
private int started = -1;
|
||||
private int position = -1;
|
||||
private int total = -1;
|
||||
private String guid;
|
||||
|
||||
public Builder(FeedItem item, Action action) {
|
||||
this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action);
|
||||
@ -233,6 +251,11 @@ public class EpisodeAction {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder guid(String guid) {
|
||||
this.guid = guid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder currentTimestamp() {
|
||||
return timestamp(new Date());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user