Sync actions regularly, local subscriptions changes overwrite remote ones

This commit is contained in:
Martin Fietz 2015-05-12 17:43:34 +02:00
parent 1d25137edd
commit 108daed5a9
3 changed files with 104 additions and 47 deletions

View File

@ -258,7 +258,7 @@ public class GpodnetEpisodeAction {
private int total = -1; private int total = -1;
public Builder(FeedItem item, Action action) { public Builder(FeedItem item, Action action) {
this(item.getFeed().getDownload_url(), item.getItemIdentifier(), action); this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action);
} }
public Builder(String podcast, String episode, Action action) { public Builder(String podcast, String episode, Action action) {

View File

@ -173,7 +173,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_REMOVED, removedFeeds); writePreference(PREF_SYNC_REMOVED, removedFeeds);
} }
feedListLock.unlock(); feedListLock.unlock();
GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
} }
public static void addRemovedFeed(String feed) { public static void addRemovedFeed(String feed) {
@ -186,7 +186,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_ADDED, addedFeeds); writePreference(PREF_SYNC_ADDED, addedFeeds);
} }
feedListLock.unlock(); feedListLock.unlock();
GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
} }
public static Set<String> getAddedFeedsCopy() { public static Set<String> getAddedFeedsCopy() {
@ -225,6 +225,7 @@ public class GpodnetPreferences {
ensurePreferencesLoaded(); ensurePreferencesLoaded();
queuedEpisodeActions.add(action); queuedEpisodeActions.add(action);
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions)); writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
} }
public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() { public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {

View File

@ -12,7 +12,6 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -22,6 +21,7 @@ import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
@ -50,17 +50,38 @@ public class GpodnetSyncService extends Service {
public static final String ARG_ACTION = "action"; public static final String ARG_ACTION = "action";
public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync"; public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
public static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
public static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
private GpodnetService service; private GpodnetService service;
private boolean syncSubscriptions = false;
private boolean syncActions = false;
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null; final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
if (action != null && action.equals(ACTION_SYNC)) { if (action != null) {
switch(action) {
case ACTION_SYNC:
syncSubscriptions = true;
syncActions = true;
break;
case ACTION_SYNC_SUBSCRIPTIONS:
syncSubscriptions = true;
break;
case ACTION_SYNC_ACTIONS:
syncActions = true;
break;
default:
Log.e(TAG, "Received invalid intent: action argument is invalid");
}
if(syncSubscriptions || syncActions) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL)); Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
syncWaiterThread.restart(); syncWaiterThread.restart();
}
} else { } else {
Log.e(TAG, "Received invalid intent: action argument is null or invalid"); Log.e(TAG, "Received invalid intent: action argument is null");
} }
return START_FLAG_REDELIVERY; return START_FLAG_REDELIVERY;
} }
@ -91,8 +112,14 @@ public class GpodnetSyncService extends Service {
stopSelf(); stopSelf();
return; return;
} }
if(syncSubscriptions) {
syncSubscriptionChanges(); syncSubscriptionChanges();
syncSubscriptions = false;
}
if(syncActions) {
syncEpisodeActions(); syncEpisodeActions();
syncActions = false;
}
stopSelf(); stopSelf();
} }
@ -100,37 +127,36 @@ public class GpodnetSyncService extends Service {
final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp(); final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
try { try {
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this); final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
GpodnetService service = tryLogin(); GpodnetService service = tryLogin();
// first sync: download all subscriptions... // first sync: download all subscriptions...
GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID(), timestamp); GpodnetPreferences.getDeviceID(), timestamp);
long lastUpdate = subscriptionChanges.getTimestamp(); long newTimeStamp = subscriptionChanges.getTimestamp();
Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges); Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
processSubscriptionChanges(localSubscriptions, subscriptionChanges); processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
Collection<String> added; if(timestamp == 0) {
Collection<String> removed; // this is this apps first sync with gpodder:
if (timestamp == 0) { // only submit changes gpodder has not just sent us
added = localSubscriptions; localAdded = localSubscriptions;
GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy()); localAdded.removeAll(subscriptionChanges.getAdded());
removed = Collections.emptyList(); localRemoved.removeAll(subscriptionChanges.getRemoved());
} else {
added = GpodnetPreferences.getAddedFeedsCopy();
removed = GpodnetPreferences.getRemovedFeedsCopy();
} }
if(added.size() > 0 || removed.size() > 0) { if(localAdded.size() > 0 || localRemoved.size() > 0) {
Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s", Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
added, removed)); localAdded, localRemoved));
GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID(), added, removed); GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
lastUpdate = uploadResponse.timestamp; newTimeStamp = uploadResponse.timestamp;
Log.d(TAG, "Upload changes response: " + uploadResponse); Log.d(TAG, "Upload changes response: " + uploadResponse);
GpodnetPreferences.removeAddedFeeds(added); GpodnetPreferences.removeAddedFeeds(localAdded);
GpodnetPreferences.removeRemovedFeeds(removed); GpodnetPreferences.removeRemovedFeeds(localRemoved);
} }
GpodnetPreferences.setLastSubscriptionSyncTimestamp(lastUpdate); GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
clearErrorNotifications(); clearErrorNotifications();
} catch (GpodnetServiceException e) { } catch (GpodnetServiceException e) {
e.printStackTrace(); e.printStackTrace();
@ -140,6 +166,27 @@ public class GpodnetSyncService extends Service {
} }
} }
private synchronized void processSubscriptionChanges(List<String> localSubscriptions,
Collection<String> localAdded,
Collection<String> localRemoved,
GpodnetSubscriptionChange changes) throws DownloadRequestException {
// local changes are always superior to remote changes!
// add subscription if (1) not already subscribed and (2) not just unsubscribed
for (String downloadUrl : changes.getAdded()) {
if (false == localSubscriptions.contains(downloadUrl) &&
false == localRemoved.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, new Date(0));
DownloadRequester.getInstance().downloadFeed(this, feed);
}
}
// remove subscription if not just subscribed (again)
for (String downloadUrl : changes.getRemoved()) {
if(false == localAdded.contains(downloadUrl)) {
DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
}
}
}
private synchronized void syncEpisodeActions() { private synchronized void syncEpisodeActions() {
final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp(); final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp();
Log.d(TAG, "last episode actions sync timestamp: " + timestamp); Log.d(TAG, "last episode actions sync timestamp: " + timestamp);
@ -173,25 +220,13 @@ public class GpodnetSyncService extends Service {
} }
} }
private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
for (String downloadUrl : changes.getAdded()) {
if (!localSubscriptions.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, new Date(0));
DownloadRequester.getInstance().downloadFeed(this, feed);
}
}
for (String downloadUrl : changes.getRemoved()) {
DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
}
}
private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions, List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException { private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException {
if(remoteActions.size() == 0) { if(remoteActions.size() == 0) {
return; return;
} }
Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>(); Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
Map<Pair<String, String>, GpodnetEpisodeAction> remoteMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
// make sure more recent local actions are not overwritten by older remote actions
for(GpodnetEpisodeAction action : localActions) { for(GpodnetEpisodeAction action : localActions) {
Pair key = new Pair(action.getPodcast(), action.getEpisode()); Pair key = new Pair(action.getPodcast(), action.getEpisode());
GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key); GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
@ -201,6 +236,9 @@ public class GpodnetSyncService extends Service {
localMostRecentPlayAction.put(key, action); localMostRecentPlayAction.put(key, action);
} }
} }
// make sure more recent local actions are not overwritten by older remote actions
Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
for (GpodnetEpisodeAction action : remoteActions) { for (GpodnetEpisodeAction action : remoteActions) {
switch (action.getAction()) { switch (action.getAction()) {
case NEW: case NEW:
@ -218,11 +256,11 @@ public class GpodnetSyncService extends Service {
GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key); GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
if(localMostRecent == null || if(localMostRecent == null ||
localMostRecent.getTimestamp().before(action.getTimestamp())) { localMostRecent.getTimestamp().before(action.getTimestamp())) {
GpodnetEpisodeAction mostRecent = remoteMostRecentPlayAction.get(key); GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
if (mostRecent == null) { if (mostRecent == null) {
remoteMostRecentPlayAction.put(key, action); mostRecentPlayAction.put(key, action);
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) { } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
remoteMostRecentPlayAction.put(key, action); mostRecentPlayAction.put(key, action);
} }
} }
break; break;
@ -231,10 +269,12 @@ public class GpodnetSyncService extends Service {
break; break;
} }
} }
for (GpodnetEpisodeAction action : remoteMostRecentPlayAction.values()) { for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode()); FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
if (playItem != null) { if (playItem != null) {
playItem.getMedia().setPosition(action.getPosition() * 1000); FeedMedia media = playItem.getMedia();
media.setPosition(action.getPosition() * 1000);
DBWriter.setFeedMedia(this, media);
if(playItem.getMedia().hasAlmostEnded()) { if(playItem.getMedia().hasAlmostEnded()) {
DBWriter.markItemRead(this, playItem, true, true); DBWriter.markItemRead(this, playItem, true, true);
DBWriter.addItemToPlaybackHistory(this, playItem.getMedia()); DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
@ -342,4 +382,20 @@ public class GpodnetSyncService extends Service {
context.startService(intent); context.startService(intent);
} }
} }
public static void sendSyncSubscriptionsIntent(Context context) {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
context.startService(intent);
}
}
public static void sendSyncActionsIntent(Context context) {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
context.startService(intent);
}
}
} }