Merge pull request #809 from mfietz/fix/gpodder-sync

Gpodder synchronization fixes
This commit is contained in:
Tom Hennen 2015-05-13 21:06:50 -04:00
commit 73c4dfc04d
5 changed files with 121 additions and 58 deletions

View File

@ -11,7 +11,20 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ViewFlipper;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
@ -22,11 +35,6 @@ import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Guides the user through the authentication process
* Step 1: Request username and password from user

View File

@ -258,7 +258,7 @@ public class GpodnetEpisodeAction {
private int total = -1;
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) {

View File

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

View File

@ -12,7 +12,6 @@ import android.util.Log;
import android.util.Pair;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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.feed.Feed;
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.GpodnetServiceAuthenticationException;
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 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 boolean syncSubscriptions = false;
private boolean syncActions = false;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
if (action != null && action.equals(ACTION_SYNC)) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
syncWaiterThread.restart();
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));
syncWaiterThread.restart();
}
} 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;
}
@ -91,8 +112,14 @@ public class GpodnetSyncService extends Service {
stopSelf();
return;
}
syncSubscriptionChanges();
syncEpisodeActions();
if(syncSubscriptions) {
syncSubscriptionChanges();
syncSubscriptions = false;
}
if(syncActions) {
syncEpisodeActions();
syncActions = false;
}
stopSelf();
}
@ -100,37 +127,36 @@ public class GpodnetSyncService extends Service {
final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
try {
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
GpodnetService service = tryLogin();
// first sync: download all subscriptions...
GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID(), timestamp);
long lastUpdate = subscriptionChanges.getTimestamp();
long newTimeStamp = subscriptionChanges.getTimestamp();
Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
processSubscriptionChanges(localSubscriptions, subscriptionChanges);
processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
Collection<String> added;
Collection<String> removed;
if (timestamp == 0) {
added = localSubscriptions;
GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy());
removed = Collections.emptyList();
} else {
added = GpodnetPreferences.getAddedFeedsCopy();
removed = GpodnetPreferences.getRemovedFeedsCopy();
if(timestamp == 0) {
// this is this apps first sync with gpodder:
// only submit changes gpodder has not just sent us
localAdded = localSubscriptions;
localAdded.removeAll(subscriptionChanges.getAdded());
localRemoved.removeAll(subscriptionChanges.getRemoved());
}
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",
added, removed));
localAdded, localRemoved));
GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID(), added, removed);
lastUpdate = uploadResponse.timestamp;
GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
newTimeStamp = uploadResponse.timestamp;
Log.d(TAG, "Upload changes response: " + uploadResponse);
GpodnetPreferences.removeAddedFeeds(added);
GpodnetPreferences.removeRemovedFeeds(removed);
GpodnetPreferences.removeAddedFeeds(localAdded);
GpodnetPreferences.removeRemovedFeeds(localRemoved);
}
GpodnetPreferences.setLastSubscriptionSyncTimestamp(lastUpdate);
GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
clearErrorNotifications();
} catch (GpodnetServiceException e) {
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() {
final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp();
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) {
return;
}
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) {
Pair key = new Pair(action.getPodcast(), action.getEpisode());
GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
@ -201,6 +236,9 @@ public class GpodnetSyncService extends Service {
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) {
switch (action.getAction()) {
case NEW:
@ -218,11 +256,11 @@ public class GpodnetSyncService extends Service {
GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
if(localMostRecent == null ||
localMostRecent.getTimestamp().before(action.getTimestamp())) {
GpodnetEpisodeAction mostRecent = remoteMostRecentPlayAction.get(key);
GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
if (mostRecent == null) {
remoteMostRecentPlayAction.put(key, action);
mostRecentPlayAction.put(key, action);
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
remoteMostRecentPlayAction.put(key, action);
mostRecentPlayAction.put(key, action);
}
}
break;
@ -231,10 +269,12 @@ public class GpodnetSyncService extends Service {
break;
}
}
for (GpodnetEpisodeAction action : remoteMostRecentPlayAction.values()) {
for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
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()) {
DBWriter.markItemRead(this, playItem, true, true);
DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
@ -342,4 +382,20 @@ public class GpodnetSyncService extends Service {
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);
}
}
}

View File

@ -171,10 +171,10 @@ public final class DBTasks {
isRefreshing.set(false);
if (FlattrUtils.hasToken()) {
if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things.");
Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status.");
Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
@ -185,9 +185,7 @@ public final class DBTasks {
}
}.start();
} else {
if (BuildConfig.DEBUG)
Log.d(TAG,
"Ignoring request to refresh all feeds: Refresh lock is locked");
Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
}
}