summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java2
-rw-r--r--core/src/main/AndroidManifest.xml5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java238
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java363
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java381
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetService.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java)390
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceAuthenticationException.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceBadStatusCodeException.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java)2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceException.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetDevice.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java)2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetEpisodeActionPostResponse.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java)13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetPodcast.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java)2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetTag.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java)2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetUploadChangesResponse.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java)20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeAction.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java)138
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeActionChanges.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java)13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/ISyncService.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/SubscriptionChanges.java (renamed from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java)12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/SyncServiceException.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/model/UploadChangesResponse.java13
-rw-r--r--core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java2
29 files changed, 700 insertions, 1079 deletions
diff --git a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
index d5617ce4b..eea7d0ace 100644
--- a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -30,8 +30,6 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
- public static GpodnetCallbacks gpodnetCallbacks;
-
public static DBTasksCallbacks dbTasksCallbacks;
public static CastCallbacks castCallbacks;
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 3b6edae42..3527ff010 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -26,9 +26,10 @@
</intent-filter>
</service>
<service
- android:name=".service.GpodnetSyncService"
+ android:name=".sync.SyncService"
android:permission="android.permission.BIND_JOB_SERVICE"
- android:enabled="true" />
+ android:enabled="true"
+ android:exported="false" />
<receiver
android:name=".receiver.MediaButtonReceiver"
diff --git a/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java
deleted file mode 100644
index 10797ecfb..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.danoeh.antennapod.core;
-
-import android.app.PendingIntent;
-import android.content.Context;
-
-/**
- * Callbacks related to the gpodder.net integration of the core module
- */
-public interface GpodnetCallbacks {
-
-
- /**
- * Returns if true if the gpodder.net integration should be activated,
- * false otherwise.
- */
- boolean gpodnetEnabled();
-
- /**
- * Returns a PendingIntent for the error notification of the GpodnetSyncService.
- * <p/>
- * What the PendingIntent does may be implementation-specific.
- *
- * @return A PendingIntent for the notification or null if gpodder.net integration
- * has been disabled (i.e. gpodnetEnabled() == false).
- */
- PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context);
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 80b205c0f..4414a03db 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -15,7 +15,6 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -25,6 +24,8 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.sync.SyncService;
+import de.danoeh.antennapod.core.sync.model.EpisodeAction;
public class FeedMedia extends FeedFile implements Playable {
private static final String TAG = "FeedMedia";
@@ -502,17 +503,14 @@ public class FeedMedia extends FeedFile implements Playable {
private void postPlaybackTasks(Context context, boolean completed) {
if (item != null) {
- // gpodder play action
- if (startPosition >= 0 && (completed || startPosition < position) &&
- GpodnetPreferences.loggedIn()) {
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.PLAY)
- .currentDeviceId()
+ if (startPosition >= 0 && (completed || startPosition < position) && GpodnetPreferences.loggedIn()) {
+ EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.PLAY)
.currentTimestamp()
.started(startPosition / 1000)
.position((completed ? duration : position) / 1000)
.total(duration / 1000)
.build();
- GpodnetPreferences.enqueueEpisodeAction(action);
+ SyncService.enqueueEpisodeAction(context, action);
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java
deleted file mode 100644
index 1127d0b67..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.danoeh.antennapod.core.gpoddernet;
-
-public class GpodnetServiceAuthenticationException extends GpodnetServiceException {
- private static final long serialVersionUID = 1L;
-
- public GpodnetServiceAuthenticationException() {
- super();
- }
-
- public GpodnetServiceAuthenticationException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public GpodnetServiceAuthenticationException(String message) {
- super(message);
- }
-
- public GpodnetServiceAuthenticationException(Throwable cause) {
- super(cause);
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java
deleted file mode 100644
index 7b99ddbc4..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.danoeh.antennapod.core.gpoddernet;
-
-public class GpodnetServiceException extends Exception {
- private static final long serialVersionUID = 1L;
-
- GpodnetServiceException() {
- }
-
- GpodnetServiceException(String message) {
- super(message);
- }
-
- public GpodnetServiceException(Throwable cause) {
- super(cause);
- }
-
- GpodnetServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
index 5b17dd338..4e7d59a48 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
@@ -2,22 +2,11 @@ package de.danoeh.antennapod.core.preferences;
import android.content.Context;
import android.content.SharedPreferences;
-import android.text.TextUtils;
import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
-
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.service.GpodnetSyncService;
+import de.danoeh.antennapod.core.sync.SyncService;
+import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
/**
* Manages preferences for accessing gpodder.net service
@@ -34,37 +23,11 @@ public class GpodnetPreferences {
private static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID";
private static final String PREF_GPODNET_HOSTNAME = "prefGpodnetHostname";
-
- private static final String PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
- private static final String PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_episode_actions_sync_timestamp";
- private static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added";
- private static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed";
- private static final String PREF_SYNC_EPISODE_ACTIONS = "de.danoeh.antennapod.preferences.gpoddernet.sync_queued_episode_actions";
- public static final String PREF_LAST_SYNC_ATTEMPT_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_attempt_timestamp";
- private static final String PREF_LAST_SYNC_ATTEMPT_RESULT = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_attempt_result";
-
private static String username;
private static String password;
private static String deviceID;
private static String hostname;
- private static final ReentrantLock feedListLock = new ReentrantLock();
- private static Set<String> addedFeeds;
- private static Set<String> removedFeeds;
-
- private static List<GpodnetEpisodeAction> queuedEpisodeActions;
-
- /**
- * Last value returned by getSubscriptionChanges call. Will be used for all subsequent calls of getSubscriptionChanges.
- */
- private static long lastSubscriptionSyncTimestamp;
-
- private static long lastEpisodeActionsSyncTimeStamp;
-
- private static long lastSyncAttemptTimestamp;
-
- private static boolean lastSyncAttemptResult;
-
private static boolean preferencesLoaded = false;
private static SharedPreferences getPreferences() {
@@ -87,13 +50,6 @@ public class GpodnetPreferences {
username = prefs.getString(PREF_GPODNET_USERNAME, null);
password = prefs.getString(PREF_GPODNET_PASSWORD, null);
deviceID = prefs.getString(PREF_GPODNET_DEVICEID, null);
- lastSubscriptionSyncTimestamp = prefs.getLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0);
- lastEpisodeActionsSyncTimeStamp = prefs.getLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0);
- lastSyncAttemptTimestamp = prefs.getLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0);
- lastSyncAttemptResult = prefs.getBoolean(PREF_LAST_SYNC_ATTEMPT_RESULT, false);
- addedFeeds = readListFromString(prefs.getString(PREF_SYNC_ADDED, ""));
- removedFeeds = readListFromString(prefs.getString(PREF_SYNC_REMOVED, ""));
- queuedEpisodeActions = readEpisodeActionsFromString(prefs.getString(PREF_SYNC_EPISODE_ACTIONS, ""));
hostname = checkGpodnetHostname(prefs.getString(PREF_GPODNET_HOSTNAME, GpodnetService.DEFAULT_BASE_HOST));
preferencesLoaded = true;
@@ -106,24 +62,6 @@ public class GpodnetPreferences {
editor.apply();
}
- private static void writePreference(String key, long value) {
- SharedPreferences.Editor editor = getPreferences().edit();
- editor.putLong(key, value);
- editor.apply();
- }
-
- private static void writePreference(String key, Collection<String> value) {
- SharedPreferences.Editor editor = getPreferences().edit();
- editor.putString(key, writeListToString(value));
- editor.apply();
- }
-
- private static void writePreference(String key, boolean value) {
- SharedPreferences.Editor editor = getPreferences().edit();
- editor.putBoolean(key, value);
- editor.apply();
- }
-
public static String getUsername() {
ensurePreferencesLoaded();
return username;
@@ -154,43 +92,6 @@ public class GpodnetPreferences {
writePreference(PREF_GPODNET_DEVICEID, deviceID);
}
- public static long getLastSubscriptionSyncTimestamp() {
- ensurePreferencesLoaded();
- return lastSubscriptionSyncTimestamp;
- }
-
- public static void setLastSubscriptionSyncTimestamp(long timestamp) {
- GpodnetPreferences.lastSubscriptionSyncTimestamp = timestamp;
- writePreference(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, timestamp);
- }
-
- public static long getLastEpisodeActionsSyncTimestamp() {
- ensurePreferencesLoaded();
- return lastEpisodeActionsSyncTimeStamp;
- }
-
- public static void setLastEpisodeActionsSyncTimestamp(long timestamp) {
- GpodnetPreferences.lastEpisodeActionsSyncTimeStamp = timestamp;
- writePreference(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, timestamp);
- }
-
- public static long getLastSyncAttemptTimestamp() {
- ensurePreferencesLoaded();
- return lastSyncAttemptTimestamp;
- }
-
- public static boolean getLastSyncAttemptResult() {
- ensurePreferencesLoaded();
- return lastSyncAttemptResult;
- }
-
- public static void setLastSyncAttempt(boolean result, long timestamp) {
- GpodnetPreferences.lastSyncAttemptResult = result;
- GpodnetPreferences.lastSyncAttemptTimestamp = timestamp;
- writePreference(PREF_LAST_SYNC_ATTEMPT_RESULT, result);
- writePreference(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, timestamp);
- }
-
public static String getHostname() {
ensurePreferencesLoaded();
return hostname;
@@ -205,92 +106,6 @@ public class GpodnetPreferences {
}
}
- public static void addAddedFeed(String feed) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- if (addedFeeds.add(feed)) {
- writePreference(PREF_SYNC_ADDED, addedFeeds);
- }
- if (removedFeeds.remove(feed)) {
- writePreference(PREF_SYNC_REMOVED, removedFeeds);
- }
- feedListLock.unlock();
- GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
- }
-
- public static void addRemovedFeed(String feed) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- if (removedFeeds.add(feed)) {
- writePreference(PREF_SYNC_REMOVED, removedFeeds);
- }
- if (addedFeeds.remove(feed)) {
- writePreference(PREF_SYNC_ADDED, addedFeeds);
- }
- feedListLock.unlock();
- GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
- }
-
- public static Set<String> getAddedFeedsCopy() {
- ensurePreferencesLoaded();
- Set<String> copy = new HashSet<>();
- feedListLock.lock();
- copy.addAll(addedFeeds);
- feedListLock.unlock();
- return copy;
- }
-
- public static void removeAddedFeeds(Collection<String> removed) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- addedFeeds.removeAll(removed);
- writePreference(PREF_SYNC_ADDED, addedFeeds);
- feedListLock.unlock();
- }
-
- public static Set<String> getRemovedFeedsCopy() {
- ensurePreferencesLoaded();
- Set<String> copy = new HashSet<>();
- feedListLock.lock();
- copy.addAll(removedFeeds);
- feedListLock.unlock();
- return copy;
- }
-
- public static void removeRemovedFeeds(Collection<String> removed) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- removedFeeds.removeAll(removed);
- writePreference(PREF_SYNC_REMOVED, removedFeeds);
- feedListLock.unlock();
- }
-
- public static void enqueueEpisodeAction(GpodnetEpisodeAction action) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- queuedEpisodeActions.add(action);
- writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
- feedListLock.unlock();
- GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
- }
-
- public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
- ensurePreferencesLoaded();
- List<GpodnetEpisodeAction> copy = new ArrayList<>();
- feedListLock.lock();
- copy.addAll(queuedEpisodeActions);
- feedListLock.unlock();
- return copy;
- }
-
- public static void removeQueuedEpisodeActions(Collection<GpodnetEpisodeAction> queued) {
- ensurePreferencesLoaded();
- feedListLock.lock();
- queuedEpisodeActions.removeAll(queued);
- writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
- feedListLock.unlock();
- }
-
/**
* Returns true if device ID, username and password have a non-null value
*/
@@ -304,57 +119,10 @@ public class GpodnetPreferences {
setUsername(null);
setPassword(null);
setDeviceID(null);
- feedListLock.lock();
- addedFeeds.clear();
- writePreference(PREF_SYNC_ADDED, addedFeeds);
- removedFeeds.clear();
- writePreference(PREF_SYNC_REMOVED, removedFeeds);
- queuedEpisodeActions.clear();
- writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
- feedListLock.unlock();
- setLastSubscriptionSyncTimestamp(0);
- setLastSyncAttempt(false, 0);
+ SyncService.clearQueue(ClientConfig.applicationCallbacks.getApplicationInstance());
UserPreferences.setGpodnetNotificationsEnabled();
}
- private static Set<String> readListFromString(String s) {
- Set<String> result = new HashSet<>();
- Collections.addAll(result, s.split(" "));
- return result;
- }
-
- private static String writeListToString(Collection<String> c) {
- StringBuilder result = new StringBuilder();
- for (String item : c) {
- result.append(item);
- result.append(" ");
- }
- return result.toString().trim();
- }
-
- private static List<GpodnetEpisodeAction> readEpisodeActionsFromString(String s) {
- String[] lines = s.split("\n");
- List<GpodnetEpisodeAction> result = new ArrayList<>(lines.length);
- for(String line : lines) {
- if(TextUtils.isEmpty(line)) {
- GpodnetEpisodeAction action = GpodnetEpisodeAction.readFromString(line);
- if(action != null) {
- result.add(GpodnetEpisodeAction.readFromString(line));
- }
- }
- }
- return result;
- }
-
- private static String writeEpisodeActionsToString(Collection<GpodnetEpisodeAction> c) {
- StringBuilder result = new StringBuilder();
- for(GpodnetEpisodeAction item : c) {
- result.append(item.writeToString());
- result.append("\n");
- }
- return result.toString();
- }
-
private static String checkGpodnetHostname(String value) {
int startIndex = 0;
if (value.startsWith("http://")) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
deleted file mode 100644
index 70fa4c91f..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
+++ /dev/null
@@ -1,363 +0,0 @@
-package de.danoeh.antennapod.core.service;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.SafeJobIntentService;
-import androidx.collection.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-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;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionGetResponse;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionPostResponse;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.DownloadRequestException;
-import de.danoeh.antennapod.core.storage.DownloadRequester;
-import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.core.util.URLChecker;
-import de.danoeh.antennapod.core.util.gui.NotificationUtils;
-
-/**
- * Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument.
- * This class also provides static methods for starting the GpodnetSyncService.
- */
-public class GpodnetSyncService extends SafeJobIntentService {
-
- private static final String TAG = "GpodnetSyncService";
-
- private static final long WAIT_INTERVAL = 5000L;
-
- private static final String ARG_ACTION = "action";
-
- private static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
- private static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
- private static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
-
- private GpodnetService service;
-
- private static final AtomicInteger syncActionCount = new AtomicInteger(0);
- private static boolean syncSubscriptions = false;
- private static boolean syncActions = false;
-
- private static final int JOB_ID = -17000;
-
- private static void enqueueWork(Context context, Intent intent) {
- enqueueWork(context, GpodnetSyncService.class, JOB_ID, intent);
- }
-
- @Override
- protected void onHandleWork(@NonNull Intent intent) {
- final String action = intent.getStringExtra(ARG_ACTION);
- 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));
- int syncActionId = syncActionCount.incrementAndGet();
- try {
- Thread.sleep(WAIT_INTERVAL);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (syncActionId == syncActionCount.get()) {
- // onHandleWork was not called again in the meantime
- sync();
- }
- }
- } else {
- Log.e(TAG, "Received invalid intent: action argument is null");
- }
- }
-
- private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
- if (service == null) {
- service = new GpodnetService();
- service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
- }
- return service;
- }
-
-
- private synchronized void sync() {
- if (!GpodnetPreferences.loggedIn() || !NetworkUtils.networkAvailable()) {
- stopForeground(true);
- stopSelf();
- return;
- }
- boolean initialSync = GpodnetPreferences.getLastSubscriptionSyncTimestamp() == 0 &&
- GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0;
- if(syncSubscriptions) {
- syncSubscriptionChanges();
- syncSubscriptions = false;
- }
- if(syncActions) {
- // we only sync episode actions after the subscriptions have been added to the database
- if(!initialSync) {
- syncEpisodeActions();
- }
- syncActions = false;
- }
- }
-
- private synchronized void syncSubscriptionChanges() {
- final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
- try {
- final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
- 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 newTimeStamp = subscriptionChanges.getTimestamp();
-
- Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
- processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
-
- 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(localAdded.size() > 0 || localRemoved.size() > 0) {
- Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
- localAdded, localRemoved));
- GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
- GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
- newTimeStamp = uploadResponse.timestamp;
- Log.d(TAG, "Upload changes response: " + uploadResponse);
- GpodnetPreferences.removeAddedFeeds(localAdded);
- GpodnetPreferences.removeRemovedFeeds(localRemoved);
- }
- GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
- GpodnetPreferences.setLastSyncAttempt(true, System.currentTimeMillis());
- clearErrorNotifications();
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- updateErrorNotification(e);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
-
- 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 (!URLChecker.containsUrl(localSubscriptions, downloadUrl) && !localRemoved.contains(downloadUrl)) {
- Feed feed = new Feed(downloadUrl, null);
- DownloadRequester.getInstance().downloadFeed(this, feed);
- }
- }
- // remove subscription if not just subscribed (again)
- for (String downloadUrl : changes.getRemoved()) {
- if (!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);
- try {
- GpodnetService service = tryLogin();
-
- // download episode actions
- GpodnetEpisodeActionGetResponse getResponse = service.getEpisodeChanges(timestamp);
- long lastUpdate = getResponse.getTimestamp();
- Log.d(TAG, "Downloaded episode actions: " + getResponse);
- List<GpodnetEpisodeAction> remoteActions = getResponse.getEpisodeActions();
-
- List<GpodnetEpisodeAction> localActions = GpodnetPreferences.getQueuedEpisodeActions();
- processEpisodeActions(localActions, remoteActions);
-
- // upload local actions
- if(localActions.size() > 0) {
- Log.d(TAG, "Uploading episode actions: " + localActions);
- GpodnetEpisodeActionPostResponse postResponse = service.uploadEpisodeActions(localActions);
- lastUpdate = postResponse.timestamp;
- Log.d(TAG, "Upload episode response: " + postResponse);
- GpodnetPreferences.removeQueuedEpisodeActions(localActions);
- }
- GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(lastUpdate);
- GpodnetPreferences.setLastSyncAttempt(true, System.currentTimeMillis());
- clearErrorNotifications();
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- updateErrorNotification(e);
- }
- }
-
-
- private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
- List<GpodnetEpisodeAction> remoteActions) {
- if(remoteActions.size() == 0) {
- return;
- }
- Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new ArrayMap<>();
- for(GpodnetEpisodeAction action : localActions) {
- Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
- GpodnetEpisodeAction 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>, GpodnetEpisodeAction> mostRecentPlayAction = new ArrayMap<>();
- for (GpodnetEpisodeAction action : remoteActions) {
- switch (action.getAction()) {
- case NEW:
- FeedItem newItem = DBReader.getFeedItem(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());
- GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
- if(localMostRecent == null ||
- localMostRecent.getTimestamp() == null ||
- localMostRecent.getTimestamp().before(action.getTimestamp())) {
- GpodnetEpisodeAction 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;
- }
- }
- for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
- FeedItem playItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
- if (playItem != null) {
- FeedMedia media = playItem.getMedia();
- media.setPosition(action.getPosition() * 1000);
- DBWriter.setFeedMedia(media);
- if(playItem.getMedia().hasAlmostEnded()) {
- DBWriter.markItemPlayed(playItem, FeedItem.PLAYED, true);
- DBWriter.addItemToPlaybackHistory(playItem.getMedia());
- }
- }
- }
- }
-
- private void clearErrorNotifications() {
- NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(R.id.notification_gpodnet_sync_error);
- nm.cancel(R.id.notification_gpodnet_sync_autherror);
- }
-
- private void updateErrorNotification(GpodnetServiceException exception) {
- Log.d(TAG, "Posting error notification");
- GpodnetPreferences.setLastSyncAttempt(false, System.currentTimeMillis());
-
- final String title;
- final String description;
- final int id;
- if (exception instanceof GpodnetServiceAuthenticationException) {
- title = getString(R.string.gpodnetsync_auth_error_title);
- description = getString(R.string.gpodnetsync_auth_error_descr);
- id = R.id.notification_gpodnet_sync_autherror;
- } else {
- if (UserPreferences.gpodnetNotificationsEnabled()) {
- title = getString(R.string.gpodnetsync_error_title);
- description = getString(R.string.gpodnetsync_error_descr) + exception.getMessage();
- id = R.id.notification_gpodnet_sync_error;
- } else {
- return;
- }
- }
-
- PendingIntent activityIntent = ClientConfig.gpodnetCallbacks.getGpodnetSyncServiceErrorNotificationPendingIntent(this);
- Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
- .setContentTitle(title)
- .setContentText(description)
- .setContentIntent(activityIntent)
- .setSmallIcon(R.drawable.ic_notification_sync_error)
- .setAutoCancel(true)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .build();
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(id, notification);
- }
-
- public static void sendSyncIntent(Context context) {
- if (GpodnetPreferences.loggedIn()) {
- Intent intent = new Intent(context, GpodnetSyncService.class);
- intent.putExtra(ARG_ACTION, ACTION_SYNC);
- enqueueWork(context, intent);
- }
- }
-
- public static void sendSyncSubscriptionsIntent(Context context) {
- if (GpodnetPreferences.loggedIn()) {
- Intent intent = new Intent(context, GpodnetSyncService.class);
- intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
- enqueueWork(context, intent);
- }
- }
-
- public static void sendSyncActionsIntent(Context context) {
- if (GpodnetPreferences.loggedIn()) {
- Intent intent = new Intent(context, GpodnetSyncService.class);
- intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
- enqueueWork(context, intent);
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index ee34746fc..1251dd96e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -17,6 +17,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import de.danoeh.antennapod.core.sync.SyncService;
import org.apache.commons.io.FileUtils;
import org.greenrobot.eventbus.EventBus;
@@ -42,9 +43,7 @@ import de.danoeh.antennapod.core.event.FeedItemEvent;
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.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
@@ -234,11 +233,7 @@ public class DownloadService extends Service {
// if this was the initial gpodder sync, i.e. we just synced the feeds successfully,
// it is now time to sync the episode actions
- if (GpodnetPreferences.loggedIn() &&
- GpodnetPreferences.getLastSubscriptionSyncTimestamp() > 0 &&
- GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0) {
- GpodnetSyncService.sendSyncActionsIntent(this);
- }
+ SyncService.sync(this);
// start auto download in case anything new has shown up
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
index 26a0e416e..9e2b69810 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
@@ -12,14 +12,14 @@ import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.sync.SyncService;
+import de.danoeh.antennapod.core.sync.model.EpisodeAction;
import org.greenrobot.eventbus.EventBus;
/**
@@ -99,12 +99,11 @@ public class MediaDownloadedHandler implements Runnable {
DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage(), request.isInitiatedByUser());
}
- if (GpodnetPreferences.loggedIn() && item != null) {
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DOWNLOAD)
- .currentDeviceId()
+ if (item != null) {
+ EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
.currentTimestamp()
.build();
- GpodnetPreferences.enqueueEpisodeAction(action);
+ SyncService.enqueueEpisodeAction(context, action);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index 0695b1d04..16e2825b4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -17,8 +17,8 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
@@ -123,9 +123,7 @@ public final class DBTasks {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- GpodnetSyncService.sendSyncIntent(context);
- }
+ SyncService.sync(context);
// Note: automatic download of episodes will be done but not here.
// Instead it is done after all feeds have been refreshed (asynchronously),
// in DownloadService.onDestroy()
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 96dfc5aa7..7625a3bfb 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
@@ -7,6 +7,8 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import de.danoeh.antennapod.core.sync.SyncService;
+import de.danoeh.antennapod.core.sync.model.EpisodeAction;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
@@ -34,7 +36,6 @@ import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -118,11 +119,10 @@ public class DBWriter {
// Gpodder: queue delete action for synchronization
if (GpodnetPreferences.loggedIn()) {
FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
- .currentDeviceId()
+ EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp()
.build();
- GpodnetPreferences.enqueueEpisodeAction(action);
+ SyncService.enqueueEpisodeAction(context, action);
}
}
EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
@@ -169,9 +169,7 @@ public class DBWriter {
adapter.removeFeed(feed);
adapter.close();
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
- }
+ SyncService.enqueueFeedRemoved(context, feed.getDownload_url());
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
// we assume we also removed download log entries for the feed or its media files.
@@ -727,10 +725,8 @@ public class DBWriter {
adapter.setCompleteFeed(feeds);
adapter.close();
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- for (Feed feed : feeds) {
- GpodnetPreferences.addAddedFeed(feed.getDownload_url());
- }
+ for (Feed feed : feeds) {
+ SyncService.enqueueFeedAdded(context, feed.getDownload_url());
}
BackupManager backupManager = new BackupManager(context);
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
new file mode 100644
index 000000000..f5584b711
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
@@ -0,0 +1,381 @@
+package de.danoeh.antennapod.core.sync;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+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.app.SafeJobIntentService;
+import androidx.core.util.Pair;
+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.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.sync.model.EpisodeAction;
+import de.danoeh.antennapod.core.sync.model.EpisodeActionChanges;
+import de.danoeh.antennapod.core.sync.model.ISyncService;
+import de.danoeh.antennapod.core.sync.model.SubscriptionChanges;
+import de.danoeh.antennapod.core.sync.model.SyncServiceException;
+import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.URLChecker;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class SyncService extends SafeJobIntentService {
+ private static final String PREF_NAME = "SyncService";
+ private static final String PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP = "last_sync_timestamp";
+ private static final String PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP = "last_episode_actions_sync_timestamp";
+ private static final String PREF_QUEUED_FEEDS_ADDED = "sync_added";
+ private static final String PREF_QUEUED_FEEDS_REMOVED = "sync_removed";
+ private static final String PREF_QUEUED_EPISODE_ACTIONS = "sync_queued_episode_actions";
+ private static final String PREF_LAST_SYNC_ATTEMPT_TIMESTAMP = "last_sync_attempt_timestamp";
+ private static final String TAG = "SyncService";
+ private static final int JOB_ID = -17000;
+ private static final Object lock = new Object();
+ private static boolean syncPending = false;
+
+ private ISyncService syncServiceImpl;
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ syncServiceImpl = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST);
+
+ if (!NetworkUtils.networkAvailable()) {
+ stopForeground(true);
+ stopSelf();
+ return;
+ }
+
+ try {
+ // Leave some time, so other actions can be queued
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ syncPending = false;
+ try {
+ syncServiceImpl.login();
+ syncSubscriptions();
+ syncEpisodeActions();
+ syncServiceImpl.logout();
+ clearErrorNotifications();
+ } catch (SyncServiceException e) {
+ e.printStackTrace();
+ updateErrorNotification(e);
+ }
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, System.currentTimeMillis()).apply();
+ }
+
+ public static void clearQueue(Context context) {
+ synchronized (lock) {
+ 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();
+ }
+ }
+
+ public static void enqueueFeedAdded(Context context, String downloadUrl) {
+ synchronized (lock) {
+ try {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_FEEDS_ADDED, "[]");
+ JSONArray queue = new JSONArray(json);
+ queue.put(downloadUrl);
+ prefs.edit().putString(PREF_QUEUED_FEEDS_ADDED, queue.toString()).apply();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ sync(context);
+ }
+
+ public static void enqueueFeedRemoved(Context context, String downloadUrl) {
+ synchronized (lock) {
+ try {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_FEEDS_REMOVED, "[]");
+ JSONArray queue = new JSONArray(json);
+ queue.put(downloadUrl);
+ prefs.edit().putString(PREF_QUEUED_FEEDS_REMOVED, queue.toString()).apply();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ sync(context);
+ }
+
+ public static void enqueueEpisodeAction(Context context, EpisodeAction action) {
+ synchronized (lock) {
+ try {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_EPISODE_ACTIONS, "[]");
+ JSONArray queue = new JSONArray(json);
+ queue.put(action.writeToJSONObject());
+ prefs.edit().putString(PREF_QUEUED_EPISODE_ACTIONS, queue.toString()).apply();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ sync(context);
+ }
+
+ public static void sync(Context context) {
+ if (!syncPending) {
+ syncPending = true;
+ enqueueWork(context, SyncService.class, JOB_ID, new Intent());
+ } else {
+ Log.d(TAG, "Ignored sync: Job already enqueued");
+ }
+ }
+
+ public static void fullSync(Context context) {
+ synchronized (lock) {
+ 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)
+ .apply();
+ }
+ sync(context);
+ }
+
+
+ private List<EpisodeAction> getQueuedEpisodeActions() {
+ ArrayList<EpisodeAction> actions = new ArrayList<>();
+ try {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_EPISODE_ACTIONS, "[]");
+ JSONArray queue = new JSONArray(json);
+ for (int i = 0; i < queue.length(); i++) {
+ actions.add(EpisodeAction.readFromJSONObject(queue.getJSONObject(i)));
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return actions;
+ }
+
+ private List<String> getQueuedRemovedFeeds() {
+ ArrayList<String> actions = new ArrayList<>();
+ try {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_FEEDS_REMOVED, "[]");
+ JSONArray queue = new JSONArray(json);
+ for (int i = 0; i < queue.length(); i++) {
+ actions.add(queue.getString(i));
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return actions;
+ }
+
+ private List<String> getQueuedAddedFeeds() {
+ ArrayList<String> actions = new ArrayList<>();
+ try {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String json = prefs.getString(PREF_QUEUED_FEEDS_ADDED, "[]");
+ JSONArray queue = new JSONArray(json);
+ for (int i = 0; i < queue.length(); i++) {
+ actions.add(queue.getString(i));
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return actions;
+ }
+
+ private void syncSubscriptions() throws SyncServiceException {
+ final long lastSync = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .getLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0);
+ final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
+ SubscriptionChanges subscriptionChanges = syncServiceImpl.getSubscriptionChanges(lastSync);
+ long newTimeStamp = subscriptionChanges.getTimestamp();
+
+ List<String> queuedRemovedFeeds = getQueuedRemovedFeeds();
+ List<String> queuedAddedFeeds = getQueuedAddedFeeds();
+
+ Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
+ for (String downloadUrl : subscriptionChanges.getAdded()) {
+ if (!URLChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) {
+ Feed feed = new Feed(downloadUrl, null);
+ try {
+ DownloadRequester.getInstance().downloadFeed(this, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // remove subscription if not just subscribed (again)
+ for (String downloadUrl : subscriptionChanges.getRemoved()) {
+ if (!queuedAddedFeeds.contains(downloadUrl)) {
+ DBTasks.removeFeedWithDownloadUrl(this, downloadUrl);
+ }
+ }
+
+ if (lastSync == 0) {
+ Log.d(TAG, "First sync. Adding all local subscriptions.");
+ queuedAddedFeeds = localSubscriptions;
+ queuedAddedFeeds.removeAll(subscriptionChanges.getAdded());
+ queuedRemovedFeeds.removeAll(subscriptionChanges.getRemoved());
+ }
+
+ if (queuedAddedFeeds.size() > 0 || queuedRemovedFeeds.size() > 0) {
+ Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", "));
+ Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", "));
+
+ synchronized (lock) {
+ UploadChangesResponse uploadResponse = syncServiceImpl
+ .uploadSubscriptionChanges(queuedAddedFeeds, queuedRemovedFeeds);
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putString(PREF_QUEUED_FEEDS_ADDED, "[]").apply();
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putString(PREF_QUEUED_FEEDS_REMOVED, "[]").apply();
+ newTimeStamp = uploadResponse.timestamp;
+ }
+ }
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, newTimeStamp).apply();
+ }
+
+ private void syncEpisodeActions() throws SyncServiceException {
+ final long lastSync = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .getLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0);
+ EpisodeActionChanges getResponse = syncServiceImpl.getEpisodeActionChanges(lastSync);
+ long newTimeStamp = getResponse.getTimestamp();
+ List<EpisodeAction> remoteActions = getResponse.getEpisodeActions();
+ processEpisodeActions(remoteActions);
+
+ // upload local actions
+ List<EpisodeAction> queuedEpisodeActions = getQueuedEpisodeActions();
+ if (queuedEpisodeActions.size() > 0) {
+ synchronized (lock) {
+ Log.d(TAG, "Uploading actions: " + StringUtils.join(queuedEpisodeActions, ", "));
+ UploadChangesResponse postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions);
+ newTimeStamp = postResponse.timestamp;
+ Log.d(TAG, "Upload episode response: " + postResponse);
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putString(PREF_QUEUED_EPISODE_ACTIONS, "[]").apply();
+ }
+ }
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
+ .putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, newTimeStamp).apply();
+ }
+
+
+ private synchronized void processEpisodeActions(List<EpisodeAction> remoteActions) {
+ if (remoteActions.size() == 0) {
+ 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) {
+ switch (action.getAction()) {
+ case NEW:
+ FeedItem newItem = DBReader.getFeedItem(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;
+ }
+ }
+
+ for (EpisodeAction action : mostRecentPlayAction.values()) {
+ FeedItem playItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
+ if (playItem != null) {
+ FeedMedia media = playItem.getMedia();
+ media.setPosition(action.getPosition() * 1000);
+ DBWriter.setFeedMedia(media);
+ if (playItem.getMedia().hasAlmostEnded()) {
+ DBWriter.markItemPlayed(playItem, FeedItem.PLAYED, true);
+ DBWriter.addItemToPlaybackHistory(playItem.getMedia());
+ }
+ }
+ }
+ }
+
+ private void clearErrorNotifications() {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(R.id.notification_gpodnet_sync_error);
+ nm.cancel(R.id.notification_gpodnet_sync_autherror);
+ }
+
+ private void updateErrorNotification(SyncServiceException exception) {
+ Log.d(TAG, "Posting error notification");
+ final String description = getString(R.string.gpodnetsync_error_descr) + exception.getMessage();
+
+ Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
+ .setContentTitle(getString(R.string.gpodnetsync_error_title))
+ .setContentText(description)
+ .setContentIntent(pendingIntent)
+ .setSmallIcon(R.drawable.ic_notification_sync_error)
+ .setAutoCancel(true)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .build();
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(R.id.notification_gpodnet_sync_error, notification);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetService.java
index 7cef4268f..678545321 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetService.java
@@ -1,7 +1,26 @@
-package de.danoeh.antennapod.core.gpoddernet;
+package de.danoeh.antennapod.core.sync.gpoddernet;
import androidx.annotation.NonNull;
-
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
+import de.danoeh.antennapod.core.sync.model.EpisodeAction;
+import de.danoeh.antennapod.core.sync.model.EpisodeActionChanges;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetEpisodeActionPostResponse;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
+import de.danoeh.antennapod.core.sync.model.ISyncService;
+import de.danoeh.antennapod.core.sync.model.SubscriptionChanges;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetUploadChangesResponse;
+import de.danoeh.antennapod.core.sync.model.SyncServiceException;
+import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
+import okhttp3.Credentials;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.apache.commons.io.Charsets;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -14,63 +33,41 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
-import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionGetResponse;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionPostResponse;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
-import okhttp3.Credentials;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-
/**
* Communicates with the gpodder.net service.
*/
-public class GpodnetService {
-
- private static final String TAG = "GpodnetService";
-
- private static final String BASE_SCHEME = "https";
-
+public class GpodnetService implements ISyncService {
public static final String DEFAULT_BASE_HOST = "gpodder.net";
- private final String BASE_HOST;
-
+ private static final String BASE_SCHEME = "https";
private static final MediaType TEXT = MediaType.parse("plain/text; charset=utf-8");
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
-
+ private final String baseHost;
private final OkHttpClient httpClient;
+ private String username = null;
+ public GpodnetService(OkHttpClient httpClient, String baseHost) {
+ this.httpClient = httpClient;
+ this.baseHost = baseHost;
+ }
- public GpodnetService() {
- httpClient = AntennapodHttpClient.getHttpClient();
- BASE_HOST = GpodnetPreferences.getHostname();
+ private void requireLoggedIn() {
+ if (username == null) {
+ throw new IllegalStateException("Not logged in");
+ }
}
/**
* Returns the [count] most used tags.
*/
- public List<GpodnetTag> getTopTags(int count)
- throws GpodnetServiceException {
+ public List<GpodnetTag> getTopTags(int count) throws GpodnetServiceException {
URL url;
try {
- url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/api/2/tags/%d.json", count), null).toURL();
+ url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/tags/%d.json", count), null).toURL();
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
@@ -80,13 +77,12 @@ public class GpodnetService {
String response = executeRequest(request);
try {
JSONArray jsonTagList = new JSONArray(response);
- List<GpodnetTag> tagList = new ArrayList<>(
- jsonTagList.length());
+ List<GpodnetTag> tagList = new ArrayList<>(jsonTagList.length());
for (int i = 0; i < jsonTagList.length(); i++) {
- JSONObject jObj = jsonTagList.getJSONObject(i);
- String title = jObj.getString("title");
- String tag = jObj.getString("tag");
- int usage = jObj.getInt("usage");
+ JSONObject jsonObject = jsonTagList.getJSONObject(i);
+ String title = jsonObject.getString("title");
+ String tag = jsonObject.getString("tag");
+ int usage = jsonObject.getInt("usage");
tagList.add(new GpodnetTag(title, tag, usage));
}
return tagList;
@@ -101,11 +97,10 @@ public class GpodnetService {
*
* @throws IllegalArgumentException if tag is null
*/
- public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag,
- int count)
+ public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag, int count)
throws GpodnetServiceException {
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@@ -125,15 +120,13 @@ public class GpodnetService {
* @param count of elements that should be returned. Must be in range 1..100.
* @throws IllegalArgumentException if count is out of range.
*/
- public List<GpodnetPodcast> getPodcastToplist(int count)
- throws GpodnetServiceException {
- if(count < 1 || count > 100) {
+ public List<GpodnetPodcast> getPodcastToplist(int count) throws GpodnetServiceException {
+ if (count < 1 || count > 100) {
throw new IllegalArgumentException("Count must be in range 1..100");
}
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/toplist/%d.json", count), null).toURL();
+ URL url = new URI(BASE_SCHEME, baseHost, String.format("/toplist/%d.json", count), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@@ -159,13 +152,12 @@ public class GpodnetService {
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public List<GpodnetPodcast> getSuggestions(int count) throws GpodnetServiceException {
- if(count < 1 || count > 100) {
+ if (count < 1 || count > 100) {
throw new IllegalArgumentException("Count must be in range 1..100");
}
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/suggestions/%d.json", count), null).toURL();
+ URL url = new URI(BASE_SCHEME, baseHost, String.format("/suggestions/%d.json", count), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@@ -185,13 +177,12 @@ public class GpodnetService {
* Must be in range 1..256. If the value is out of range, the
* default value defined by the gpodder.net API will be used.
*/
- public List<GpodnetPodcast> searchPodcasts(String query, int scaledLogoSize)
- throws GpodnetServiceException {
+ public List<GpodnetPodcast> searchPodcasts(String query, int scaledLogoSize) throws GpodnetServiceException {
String parameters = (scaledLogoSize > 0 && scaledLogoSize <= 256) ? String
.format("q=%s&scale_logo=%d", query, scaledLogoSize) : String
.format("q=%s", query);
try {
- URL url = new URI(BASE_SCHEME, null, BASE_HOST, -1, "/search.json",
+ URL url = new URI(BASE_SCHEME, null, baseHost, -1, "/search.json",
parameters, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@@ -213,16 +204,12 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
- * @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public List<GpodnetDevice> getDevices(@NonNull String username)
- throws GpodnetServiceException {
+ public List<GpodnetDevice> getDevices() throws GpodnetServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/api/2/devices/%s.json", username), null).toURL();
+ URL url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/devices/%s.json", username), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
JSONArray devicesArray = new JSONArray(response);
@@ -238,19 +225,14 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @param deviceId The ID of the device that should be configured.
- * @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public void configureDevice(@NonNull String username,
- @NonNull String deviceId,
- String caption,
- GpodnetDevice.DeviceType type)
+ public void configureDevice(@NonNull String deviceId, String caption, GpodnetDevice.DeviceType type)
throws GpodnetServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/devices/%s/%s.json", username, deviceId), null).toURL();
String content;
if (caption != null || type != null) {
@@ -279,18 +261,14 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @param deviceId The ID of the device whose subscriptions should be returned.
* @return A list of subscriptions in OPML format.
- * @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public String getSubscriptionsOfDevice(@NonNull String username,
- @NonNull String deviceId)
- throws GpodnetServiceException {
+ public String getSubscriptionsOfDevice(@NonNull String deviceId) throws GpodnetServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/subscriptions/%s/%s.opml", username, deviceId), null).toURL();
Request.Builder request = new Request.Builder().url(url);
return executeRequest(request);
@@ -305,18 +283,14 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @return A list of subscriptions in OPML format.
* @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public String getSubscriptionsOfUser(@NonNull String username)
- throws GpodnetServiceException {
-
+ public String getSubscriptionsOfUser() throws GpodnetServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/subscriptions/%s.opml", username), null).toURL();
+ URL url = new URI(BASE_SCHEME, baseHost, String.format("/subscriptions/%s.opml", username), null).toURL();
Request.Builder request = new Request.Builder().url(url);
return executeRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
@@ -330,21 +304,17 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @param deviceId The ID of the device whose subscriptions should be updated.
* @param subscriptions A list of feed URLs containing all subscriptions of the
* device.
* @throws IllegalArgumentException If username, deviceId or subscriptions is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public void uploadSubscriptions(@NonNull String username,
- @NonNull String deviceId,
- @NonNull List<String> subscriptions)
+ public void uploadSubscriptions(@NonNull String deviceId, @NonNull List<String> subscriptions)
throws GpodnetServiceException {
-
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/subscriptions/%s/%s.txt", username, deviceId), null).toURL();
StringBuilder builder = new StringBuilder();
for (String s : subscriptions) {
@@ -366,25 +336,19 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @param deviceId The ID of the device whose subscriptions should be updated.
* @param added Collection of feed URLs of added feeds. This Collection MUST NOT contain any duplicates
* @param removed Collection of feed URLs of removed feeds. This Collection MUST NOT contain any duplicates
- * @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse}
+ * @return a GpodnetUploadChangesResponse. See {@link GpodnetUploadChangesResponse}
* for details.
- * @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
- * @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
- * is an authentication error.
+ * @throws GpodnetServiceException if added or removed contain duplicates or if there
+ * is an authentication error.
*/
- public GpodnetUploadChangesResponse uploadChanges(@NonNull String username,
- @NonNull String deviceId,
- @NonNull Collection<String> added,
- @NonNull Collection<String> removed)
- throws GpodnetServiceException {
-
+ public GpodnetUploadChangesResponse uploadChanges(@NonNull String deviceId, @NonNull Collection<String> added,
+ @NonNull Collection<String> removed) throws GpodnetServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/subscriptions/%s/%s.json", username, deviceId), null).toURL();
final JSONObject requestObject = new JSONObject();
@@ -408,24 +372,19 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param username The username. Must be the same user as the one which is
- * currently logged in.
* @param deviceId The ID of the device whose subscription changes should be
* downloaded.
* @param timestamp A timestamp that can be used to receive all changes since a
* specific point in time.
- * @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public GpodnetSubscriptionChange getSubscriptionChanges(@NonNull String username,
- @NonNull String deviceId,
- long timestamp) throws GpodnetServiceException {
-
+ public SubscriptionChanges getSubscriptionChanges(@NonNull String deviceId, long timestamp)
+ throws GpodnetServiceException {
+ requireLoggedIn();
String params = String.format("since=%d", timestamp);
- String path = String.format("/api/2/subscriptions/%s/%s.json",
- username, deviceId);
+ String path = String.format("/api/2/subscriptions/%s/%s.json", username, deviceId);
try {
- URL url = new URI(BASE_SCHEME, null, BASE_HOST, -1, path, params,
+ URL url = new URI(BASE_SCHEME, null, baseHost, -1, path, params,
null).toURL();
Request.Builder request = new Request.Builder().url(url);
@@ -447,26 +406,24 @@ public class GpodnetService {
* <p/>
* This method requires authentication.
*
- * @param episodeActions Collection of episode actions.
- * @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse}
+ * @param episodeActions Collection of episode actions.
+ * @return a GpodnetUploadChangesResponse. See {@link GpodnetUploadChangesResponse}
* for details.
- * @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
- * @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
- * is an authentication error.
+ * @throws GpodnetServiceException if added or removed contain duplicates or if there
+ * is an authentication error.
*/
- public GpodnetEpisodeActionPostResponse uploadEpisodeActions(@NonNull Collection<GpodnetEpisodeAction> episodeActions)
- throws GpodnetServiceException {
-
- String username = GpodnetPreferences.getUsername();
-
+ @Override
+ public UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> episodeActions) throws SyncServiceException {
+ requireLoggedIn();
try {
- URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/episodes/%s.json", username), null).toURL();
final JSONArray list = new JSONArray();
- for(GpodnetEpisodeAction episodeAction : episodeActions) {
+ for (EpisodeAction episodeAction : episodeActions) {
JSONObject obj = episodeAction.writeToJSONObject();
- if(obj != null) {
+ if (obj != null) {
+ obj.put("device", GpodnetPreferences.getDeviceID());
list.put(obj);
}
}
@@ -478,7 +435,7 @@ public class GpodnetService {
return GpodnetEpisodeActionPostResponse.fromJSONObject(response);
} catch (JSONException | MalformedURLException | URISyntaxException e) {
e.printStackTrace();
- throw new GpodnetServiceException(e);
+ throw new SyncServiceException(e);
}
}
@@ -490,19 +447,15 @@ public class GpodnetService {
*
* @param timestamp A timestamp that can be used to receive all changes since a
* specific point in time.
- * @throws IllegalArgumentException If username or deviceId is null.
- * @throws GpodnetServiceAuthenticationException If there is an authentication error.
+ * @throws SyncServiceException If there is an authentication error.
*/
- public GpodnetEpisodeActionGetResponse getEpisodeChanges(long timestamp) throws GpodnetServiceException {
-
- String username = GpodnetPreferences.getUsername();
-
+ @Override
+ public EpisodeActionChanges getEpisodeActionChanges(long timestamp) throws SyncServiceException {
+ requireLoggedIn();
String params = String.format("since=%d", timestamp);
- String path = String.format("/api/2/episodes/%s.json",
- username);
+ String path = String.format("/api/2/episodes/%s.json", username);
try {
- URL url = new URI(BASE_SCHEME, null, BASE_HOST, -1, path, params,
- null).toURL();
+ URL url = new URI(BASE_SCHEME, null, baseHost, -1, path, params, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@@ -513,7 +466,7 @@ public class GpodnetService {
throw new IllegalStateException(e);
} catch (JSONException | MalformedURLException e) {
e.printStackTrace();
- throw new GpodnetServiceException(e);
+ throw new SyncServiceException(e);
}
}
@@ -525,33 +478,27 @@ public class GpodnetService {
*
* @throws IllegalArgumentException If username or password is null.
*/
- public void authenticate(@NonNull String username,
- @NonNull String password)
- throws GpodnetServiceException {
+ public void authenticate(@NonNull String username, @NonNull String password) throws GpodnetServiceException {
URL url;
try {
- url = new URI(BASE_SCHEME, BASE_HOST, String.format(
- "/api/2/auth/%s/login.json", username), null).toURL();
+ url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/auth/%s/login.json", username), null).toURL();
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
- RequestBody body = RequestBody.create(TEXT, "");
- Request.Builder request = new Request.Builder().url(url).post(body);
- executeRequestWithAuthentication(request, username, password);
- }
-
- /**
- * Shuts down the GpodnetService's HTTP client. The service will be shut down in a separate thread to avoid
- * NetworkOnMainThreadExceptions.
- */
- public void shutdown() {
- new Thread() {
- @Override
- public void run() {
- AntennapodHttpClient.cleanup();
- }
- }.start();
+ RequestBody requestBody = RequestBody.create(TEXT, "");
+ Request request = new Request.Builder().url(url).post(requestBody).build();
+ try {
+ String credential = Credentials.basic(username, password, Charsets.UTF_8);
+ Request authRequest = request.newBuilder().header("Authorization", credential).build();
+ Response response = httpClient.newCall(authRequest).execute();
+ checkStatusCode(response);
+ response.body().close();
+ this.username = username;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new GpodnetServiceException(e);
+ }
}
private String executeRequest(@NonNull Request.Builder requestB) throws GpodnetServiceException {
@@ -576,36 +523,7 @@ public class GpodnetService {
return responseString;
}
- private String executeRequestWithAuthentication(Request.Builder requestB,
- String username, String password) throws GpodnetServiceException {
- if (requestB == null || username == null || password == null) {
- throw new IllegalArgumentException(
- "request and credentials must not be null");
- }
-
- Request request = requestB.build();
- String result = null;
- ResponseBody body = null;
- try {
- String credential = Credentials.basic(username, password, Charset.forName("UTF-8"));
- Request authRequest = request.newBuilder().header("Authorization", credential).build();
- Response response = httpClient.newCall(authRequest).execute();
- checkStatusCode(response);
- body = response.body();
- result = getStringFromResponseBody(body);
- } catch (Exception e) {
- e.printStackTrace();
- throw new GpodnetServiceException(e);
- } finally {
- if (body != null) {
- body.close();
- }
- }
- return result;
- }
-
- private String getStringFromResponseBody(@NonNull ResponseBody body)
- throws GpodnetServiceException {
+ private String getStringFromResponseBody(@NonNull ResponseBody body) throws GpodnetServiceException {
ByteArrayOutputStream outputStream;
int contentLength = (int) body.contentLength();
if (contentLength > 0) {
@@ -627,36 +545,31 @@ public class GpodnetService {
return outputStream.toString();
}
- private void checkStatusCode(@NonNull Response response)
- throws GpodnetServiceException {
+ private void checkStatusCode(@NonNull Response response) throws GpodnetServiceException {
int responseCode = response.code();
if (responseCode != HttpURLConnection.HTTP_OK) {
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw new GpodnetServiceAuthenticationException("Wrong username or password");
} else {
- throw new GpodnetServiceBadStatusCodeException("Bad response code: "
- + responseCode, responseCode);
+ throw new GpodnetServiceBadStatusCodeException("Bad response code: " + responseCode, responseCode);
}
}
}
- private List<GpodnetPodcast> readPodcastListFromJSONArray(@NonNull JSONArray array)
- throws JSONException {
- List<GpodnetPodcast> result = new ArrayList<>(
- array.length());
+ private List<GpodnetPodcast> readPodcastListFromJSONArray(@NonNull JSONArray array) throws JSONException {
+ List<GpodnetPodcast> result = new ArrayList<>(array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readPodcastFromJSONObject(array.getJSONObject(i)));
}
return result;
}
- private GpodnetPodcast readPodcastFromJSONObject(JSONObject object)
- throws JSONException {
+ private GpodnetPodcast readPodcastFromJSONObject(JSONObject object) throws JSONException {
String url = object.getString("url");
String title;
Object titleObj = object.opt("title");
- if (titleObj != null && titleObj instanceof String) {
+ if (titleObj instanceof String) {
title = (String) titleObj;
} else {
title = url;
@@ -664,7 +577,7 @@ public class GpodnetService {
String description;
Object descriptionObj = object.opt("description");
- if (descriptionObj != null && descriptionObj instanceof String) {
+ if (descriptionObj instanceof String) {
description = (String) descriptionObj;
} else {
description = "";
@@ -673,49 +586,38 @@ public class GpodnetService {
int subscribers = object.getInt("subscribers");
Object logoUrlObj = object.opt("logo_url");
- String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj
- : null;
+ String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj : null;
if (logoUrl == null) {
Object scaledLogoUrl = object.opt("scaled_logo_url");
- if (scaledLogoUrl != null && scaledLogoUrl instanceof String) {
+ if (scaledLogoUrl instanceof String) {
logoUrl = (String) scaledLogoUrl;
}
}
String website = null;
Object websiteObj = object.opt("website");
- if (websiteObj != null && websiteObj instanceof String) {
+ if (websiteObj instanceof String) {
website = (String) websiteObj;
}
String mygpoLink = object.getString("mygpo_link");
String author = null;
Object authorObj = object.opt("author");
- if (authorObj != null && authorObj instanceof String) {
+ if (authorObj instanceof String) {
author = (String) authorObj;
}
- return new GpodnetPodcast(url,
- title,
- description,
- subscribers,
- logoUrl,
- website,
- mygpoLink,
- author);
+ return new GpodnetPodcast(url, title, description, subscribers, logoUrl, website, mygpoLink, author);
}
- private List<GpodnetDevice> readDeviceListFromJSONArray(@NonNull JSONArray array)
- throws JSONException {
- List<GpodnetDevice> result = new ArrayList<>(
- array.length());
+ private List<GpodnetDevice> readDeviceListFromJSONArray(@NonNull JSONArray array) throws JSONException {
+ List<GpodnetDevice> result = new ArrayList<>(array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readDeviceFromJSONObject(array.getJSONObject(i)));
}
return result;
}
- private GpodnetDevice readDeviceFromJSONObject(JSONObject object)
- throws JSONException {
+ private GpodnetDevice readDeviceFromJSONObject(JSONObject object) throws JSONException {
String id = object.getString("id");
String caption = object.getString("caption");
String type = object.getString("type");
@@ -723,8 +625,8 @@ public class GpodnetService {
return new GpodnetDevice(id, caption, type, subscriptions);
}
- private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject(
- @NonNull JSONObject object) throws JSONException {
+ private SubscriptionChanges readSubscriptionChangesFromJSONObject(@NonNull JSONObject object)
+ throws JSONException {
List<String> added = new LinkedList<>();
JSONArray jsonAdded = object.getJSONArray("add");
@@ -745,24 +647,44 @@ public class GpodnetService {
}
long timestamp = object.getLong("timestamp");
- return new GpodnetSubscriptionChange(added, removed, timestamp);
+ return new SubscriptionChanges(added, removed, timestamp);
}
- private GpodnetEpisodeActionGetResponse readEpisodeActionsFromJSONObject(
- @NonNull JSONObject object) throws JSONException {
+ private EpisodeActionChanges readEpisodeActionsFromJSONObject(@NonNull JSONObject object)
+ throws JSONException {
- List<GpodnetEpisodeAction> episodeActions = new ArrayList<>();
+ List<EpisodeAction> episodeActions = new ArrayList<>();
long timestamp = object.getLong("timestamp");
JSONArray jsonActions = object.getJSONArray("actions");
- for(int i=0; i < jsonActions.length(); i++) {
+ for (int i = 0; i < jsonActions.length(); i++) {
JSONObject jsonAction = jsonActions.getJSONObject(i);
- GpodnetEpisodeAction episodeAction = GpodnetEpisodeAction.readFromJSONObject(jsonAction);
- if(episodeAction != null) {
+ EpisodeAction episodeAction = EpisodeAction.readFromJSONObject(jsonAction);
+ if (episodeAction != null) {
episodeActions.add(episodeAction);
}
}
- return new GpodnetEpisodeActionGetResponse(episodeActions, timestamp);
+ return new EpisodeActionChanges(episodeActions, timestamp);
+ }
+
+ @Override
+ public void login() throws GpodnetServiceException {
+ authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
+ }
+
+ @Override
+ public SubscriptionChanges getSubscriptionChanges(long lastSync) throws GpodnetServiceException {
+ return getSubscriptionChanges(GpodnetPreferences.getDeviceID(), lastSync);
+ }
+
+ @Override
+ public UploadChangesResponse uploadSubscriptionChanges(List<String> addedFeeds, List<String> removedFeeds)
+ throws GpodnetServiceException {
+ return uploadChanges(GpodnetPreferences.getDeviceID(), addedFeeds, removedFeeds);
}
+ @Override
+ public void logout() {
+
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceAuthenticationException.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceAuthenticationException.java
new file mode 100644
index 000000000..0aec8e97e
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceAuthenticationException.java
@@ -0,0 +1,9 @@
+package de.danoeh.antennapod.core.sync.gpoddernet;
+
+public class GpodnetServiceAuthenticationException extends GpodnetServiceException {
+ private static final long serialVersionUID = 1L;
+
+ public GpodnetServiceAuthenticationException(String message) {
+ super(message);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceBadStatusCodeException.java
index 54c3bc7c8..c24b5fc0a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceBadStatusCodeException.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.gpoddernet;
+package de.danoeh.antennapod.core.sync.gpoddernet;
class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
private static final long serialVersionUID = 1L;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceException.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceException.java
new file mode 100644
index 000000000..10c4fdc11
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/GpodnetServiceException.java
@@ -0,0 +1,15 @@
+package de.danoeh.antennapod.core.sync.gpoddernet;
+
+import de.danoeh.antennapod.core.sync.model.SyncServiceException;
+
+public class GpodnetServiceException extends SyncServiceException {
+ private static final long serialVersionUID = 1L;
+
+ public GpodnetServiceException(String message) {
+ super(message);
+ }
+
+ public GpodnetServiceException(Throwable e) {
+ super(e);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetDevice.java
index e86b74164..454b3301d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetDevice.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.gpoddernet.model;
import androidx.annotation.NonNull;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
index 10ea4cd9b..ae9ab9d70 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
@@ -1,7 +1,8 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.gpoddernet.model;
import androidx.collection.ArrayMap;
+import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.json.JSONArray;
@@ -10,13 +11,7 @@ import org.json.JSONObject;
import java.util.Map;
-public class GpodnetEpisodeActionPostResponse {
-
- /**
- * timestamp/ID that can be used for requesting changes since this upload.
- */
- public final long timestamp;
-
+public class GpodnetEpisodeActionPostResponse extends UploadChangesResponse {
/**
* URLs that should be updated. The key of the map is the original URL, the value of the map
* is the sanitized URL.
@@ -24,7 +19,7 @@ public class GpodnetEpisodeActionPostResponse {
private final Map<String, String> updatedUrls;
private GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
- this.timestamp = timestamp;
+ super(timestamp);
this.updatedUrls = updatedUrls;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetPodcast.java
index 5433e3ee0..bc4969758 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetPodcast.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.gpoddernet.model;
import androidx.annotation.NonNull;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetTag.java
index dec3be7f2..93abf4688 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetTag.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.gpoddernet.model;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetUploadChangesResponse.java
index ba3db8412..790ba547f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/gpoddernet/model/GpodnetUploadChangesResponse.java
@@ -1,7 +1,9 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.gpoddernet.model;
import androidx.collection.ArrayMap;
+import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -9,23 +11,17 @@ import org.json.JSONObject;
import java.util.Map;
/**
- * Object returned by {@link de.danoeh.antennapod.core.gpoddernet.GpodnetService} in uploadChanges method.
+ * Object returned by {@link GpodnetService} in uploadChanges method.
*/
-public class GpodnetUploadChangesResponse {
-
- /**
- * timestamp/ID that can be used for requesting changes since this upload.
- */
- public final long timestamp;
-
+public class GpodnetUploadChangesResponse extends UploadChangesResponse {
/**
* URLs that should be updated. The key of the map is the original URL, the value of the map
* is the sanitized URL.
*/
- private final Map<String, String> updatedUrls;
+ public final Map<String, String> updatedUrls;
- private GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
- this.timestamp = timestamp;
+ public GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
+ super(timestamp);
this.updatedUrls = updatedUrls;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeAction.java
index 71129f2e2..c05e06752 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeAction.java
@@ -1,9 +1,10 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
-
+package de.danoeh.antennapod.core.sync.model;
import android.text.TextUtils;
import android.util.Log;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.util.DateUtils;
import org.json.JSONException;
import org.json.JSONObject;
@@ -12,32 +13,25 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.util.DateUtils;
-
-public class GpodnetEpisodeAction {
-
- private static final String TAG = "GpodnetEpisodeAction";
-
- public enum Action {
- NEW, DOWNLOAD, PLAY, DELETE
- }
+public class EpisodeAction {
+ private static final String TAG = "EpisodeAction";
+ public static final Action NEW = Action.NEW;
+ public static final Action DOWNLOAD = Action.DOWNLOAD;
+ public static final Action PLAY = Action.PLAY;
+ public static final Action DELETE = Action.DELETE;
private final String podcast;
private final String episode;
- private final String deviceId;
private final Action action;
private final Date timestamp;
private final int started;
private final int position;
private final int total;
- private GpodnetEpisodeAction(Builder builder) {
+ private EpisodeAction(Builder builder) {
this.podcast = builder.podcast;
this.episode = builder.episode;
this.action = builder.action;
- this.deviceId = builder.deviceId;
this.timestamp = builder.timestamp;
this.started = builder.started;
this.position = builder.position;
@@ -45,71 +39,39 @@ public class GpodnetEpisodeAction {
}
/**
- * Creates an episode action object from a String representation. The representation includes
- * all mandatory and optional attributes
- *
- * @param s String representation (output from {@link #writeToString()})
- * @return episode action object, or null if s is invalid
- */
- public static GpodnetEpisodeAction readFromString(String s) {
- String[] fields = s.split("\t");
- if(fields.length != 8) {
- return null;
- }
- String podcast = fields[0];
- String episode = fields[1];
- String deviceId = fields[2];
- try {
- Action action = Action.valueOf(fields[3]);
- return new Builder(podcast, episode, action)
- .deviceId(deviceId)
- .timestamp(new Date(Long.parseLong(fields[4])))
- .started(Integer.parseInt(fields[5]))
- .position(Integer.parseInt(fields[6]))
- .total(Integer.parseInt(fields[7]))
- .build();
- } catch(IllegalArgumentException e) {
- Log.e(TAG, "readFromString(" + s + "): " + e.getMessage());
- return null;
- }
- }
-
- /**
* Create an episode action object from JSON representation. Mandatory fields are "podcast",
* "episode" and "action".
*
- * @param object JSON representation
- * @return episode action object, or null if mandatory values are missing
+ * @param object JSON representation
+ * @return episode action object, or null if mandatory values are missing
*/
- public static GpodnetEpisodeAction readFromJSONObject(JSONObject object) {
+ public static EpisodeAction readFromJSONObject(JSONObject object) {
String podcast = object.optString("podcast", null);
String episode = object.optString("episode", null);
String actionString = object.optString("action", null);
- if(TextUtils.isEmpty(podcast) || TextUtils.isEmpty(episode) || TextUtils.isEmpty(actionString)) {
+ if (TextUtils.isEmpty(podcast) || TextUtils.isEmpty(episode) || TextUtils.isEmpty(actionString)) {
return null;
}
- GpodnetEpisodeAction.Action action;
+ EpisodeAction.Action action;
try {
- action = GpodnetEpisodeAction.Action.valueOf(actionString.toUpperCase());
+ action = EpisodeAction.Action.valueOf(actionString.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
- String deviceId = object.optString("device", "");
- GpodnetEpisodeAction.Builder builder = new GpodnetEpisodeAction.Builder(podcast, episode, action)
- .deviceId(deviceId);
+ EpisodeAction.Builder builder = new EpisodeAction.Builder(podcast, episode, action);
String utcTimestamp = object.optString("timestamp", null);
- if(!TextUtils.isEmpty(utcTimestamp)) {
+ if (!TextUtils.isEmpty(utcTimestamp)) {
builder.timestamp(DateUtils.parse(utcTimestamp));
}
- if(action == GpodnetEpisodeAction.Action.PLAY) {
+ if (action == EpisodeAction.Action.PLAY) {
int started = object.optInt("started", -1);
int position = object.optInt("position", -1);
int total = object.optInt("total", -1);
- if(started >= 0 && position > 0 && total > 0) {
+ if (started >= 0 && position > 0 && total > 0) {
builder
- .started(started)
- .position(position)
- .total(total);
+ .started(started)
+ .position(position)
+ .total(total);
}
}
return builder.build();
@@ -123,10 +85,6 @@ public class GpodnetEpisodeAction {
return this.episode;
}
- public String getDeviceId() {
- return this.deviceId;
- }
-
public Action getAction() {
return this.action;
}
@@ -169,17 +127,17 @@ public class GpodnetEpisodeAction {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof GpodnetEpisodeAction)) return false;
+ if (o == null || !(o instanceof EpisodeAction)) return false;
- GpodnetEpisodeAction that = (GpodnetEpisodeAction) o;
+ EpisodeAction that = (EpisodeAction) o;
if (started != that.started) return false;
if (position != that.position) return false;
if (total != that.total) return false;
if (podcast != null ? !podcast.equals(that.podcast) : that.podcast != null) return false;
if (episode != null ? !episode.equals(that.episode) : that.episode != null) return false;
- if (deviceId != null ? !deviceId.equals(that.deviceId) : that.deviceId != null)
- return false;
+ //if (deviceId != null ? !deviceId.equals(that.deviceId) : that.deviceId != null)
+ // return false;
if (action != that.action) return false;
return !(timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null);
@@ -189,7 +147,6 @@ public class GpodnetEpisodeAction {
public int hashCode() {
int result = podcast != null ? podcast.hashCode() : 0;
result = 31 * result + (episode != null ? episode.hashCode() : 0);
- result = 31 * result + (deviceId != null ? deviceId.hashCode() : 0);
result = 31 * result + (action != null ? action.hashCode() : 0);
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
result = 31 * result + started;
@@ -198,17 +155,6 @@ public class GpodnetEpisodeAction {
return result;
}
- public String writeToString() {
- return this.podcast + "\t"
- + this.episode + "\t"
- + this.deviceId + "\t"
- + this.action + "\t"
- + this.timestamp.getTime() + "\t"
- + this.started + "\t"
- + this.position + "\t"
- + this.total;
- }
-
/**
* Returns a JSON object representation of this object
*
@@ -219,17 +165,16 @@ public class GpodnetEpisodeAction {
try {
obj.putOpt("podcast", this.podcast);
obj.putOpt("episode", this.episode);
- obj.put("device", this.deviceId);
obj.put("action", this.getActionString());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
- obj.put("timestamp",formatter.format(this.timestamp));
+ obj.put("timestamp", formatter.format(this.timestamp));
if (this.getAction() == Action.PLAY) {
obj.put("started", this.started);
obj.put("position", this.position);
obj.put("total", this.total);
}
- } catch(JSONException e) {
+ } catch (JSONException e) {
Log.e(TAG, "writeToJSONObject(): " + e.getMessage());
return null;
}
@@ -241,7 +186,6 @@ public class GpodnetEpisodeAction {
return "GpodnetEpisodeAction{" +
"podcast='" + podcast + '\'' +
", episode='" + episode + '\'' +
- ", deviceId='" + deviceId + '\'' +
", action=" + action +
", timestamp=" + timestamp +
", started=" + started +
@@ -250,6 +194,10 @@ public class GpodnetEpisodeAction {
'}';
}
+ public enum Action {
+ NEW, DOWNLOAD, PLAY, DELETE
+ }
+
public static class Builder {
// mandatory
@@ -258,7 +206,6 @@ public class GpodnetEpisodeAction {
private final Action action;
// optional
- private String deviceId = "";
private Date timestamp;
private int started = -1;
private int position = -1;
@@ -274,15 +221,6 @@ public class GpodnetEpisodeAction {
this.action = action;
}
- public Builder deviceId(String deviceId) {
- this.deviceId = deviceId;
- return this;
- }
-
- public Builder currentDeviceId() {
- return deviceId(GpodnetPreferences.getDeviceID());
- }
-
public Builder timestamp(Date timestamp) {
this.timestamp = timestamp;
return this;
@@ -293,28 +231,28 @@ public class GpodnetEpisodeAction {
}
public Builder started(int seconds) {
- if(action == Action.PLAY) {
+ if (action == Action.PLAY) {
this.started = seconds;
}
return this;
}
public Builder position(int seconds) {
- if(action == Action.PLAY) {
+ if (action == Action.PLAY) {
this.position = seconds;
}
return this;
}
public Builder total(int seconds) {
- if(action == Action.PLAY) {
+ if (action == Action.PLAY) {
this.total = seconds;
}
return this;
}
- public GpodnetEpisodeAction build() {
- return new GpodnetEpisodeAction(this);
+ public EpisodeAction build() {
+ return new EpisodeAction(this);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeActionChanges.java
index 7b28bba49..69c1b5280 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/EpisodeActionChanges.java
@@ -1,22 +1,21 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.model;
import androidx.annotation.NonNull;
import java.util.List;
-public class GpodnetEpisodeActionGetResponse {
+public class EpisodeActionChanges {
- private final List<GpodnetEpisodeAction> episodeActions;
+ private final List<EpisodeAction> episodeActions;
private final long timestamp;
- public GpodnetEpisodeActionGetResponse(@NonNull List<GpodnetEpisodeAction> episodeActions,
- long timestamp) {
+ public EpisodeActionChanges(@NonNull List<EpisodeAction> episodeActions, long timestamp) {
this.episodeActions = episodeActions;
this.timestamp = timestamp;
}
- public List<GpodnetEpisodeAction> getEpisodeActions() {
+ public List<EpisodeAction> getEpisodeActions() {
return this.episodeActions;
}
@@ -26,7 +25,7 @@ public class GpodnetEpisodeActionGetResponse {
@Override
public String toString() {
- return "GpodnetEpisodeActionGetResponse{" +
+ return "EpisodeActionGetResponse{" +
"episodeActions=" + episodeActions +
", timestamp=" + timestamp +
'}';
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/model/ISyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/ISyncService.java
new file mode 100644
index 000000000..473072b97
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/ISyncService.java
@@ -0,0 +1,20 @@
+package de.danoeh.antennapod.core.sync.model;
+
+import java.util.List;
+
+public interface ISyncService {
+
+ void login() throws SyncServiceException;
+
+ SubscriptionChanges getSubscriptionChanges(long lastSync) throws SyncServiceException;
+
+ UploadChangesResponse uploadSubscriptionChanges(
+ List<String> addedFeeds, List<String> removedFeeds) throws SyncServiceException;
+
+ EpisodeActionChanges getEpisodeActionChanges(long lastSync) throws SyncServiceException;
+
+ UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> queuedEpisodeActions)
+ throws SyncServiceException;
+
+ void logout() throws SyncServiceException;
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/SubscriptionChanges.java
index 56a64053f..51f2ed10d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/SubscriptionChanges.java
@@ -1,17 +1,17 @@
-package de.danoeh.antennapod.core.gpoddernet.model;
+package de.danoeh.antennapod.core.sync.model;
import androidx.annotation.NonNull;
import java.util.List;
-public class GpodnetSubscriptionChange {
+public class SubscriptionChanges {
private final List<String> added;
private final List<String> removed;
private final long timestamp;
- public GpodnetSubscriptionChange(@NonNull List<String> added,
- @NonNull List<String> removed,
- long timestamp) {
+ public SubscriptionChanges(@NonNull List<String> added,
+ @NonNull List<String> removed,
+ long timestamp) {
this.added = added;
this.removed = removed;
this.timestamp = timestamp;
@@ -19,7 +19,7 @@ public class GpodnetSubscriptionChange {
@Override
public String toString() {
- return "GpodnetSubscriptionChange [added=" + added.toString()
+ return "SubscriptionChange [added=" + added.toString()
+ ", removed=" + removed.toString() + ", timestamp="
+ timestamp + "]";
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/model/SyncServiceException.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/SyncServiceException.java
new file mode 100644
index 000000000..d7e999b45
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/SyncServiceException.java
@@ -0,0 +1,13 @@
+package de.danoeh.antennapod.core.sync.model;
+
+public class SyncServiceException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public SyncServiceException(String message) {
+ super(message);
+ }
+
+ public SyncServiceException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/model/UploadChangesResponse.java b/core/src/main/java/de/danoeh/antennapod/core/sync/model/UploadChangesResponse.java
new file mode 100644
index 000000000..44850bb03
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/sync/model/UploadChangesResponse.java
@@ -0,0 +1,13 @@
+package de.danoeh.antennapod.core.sync.model;
+
+public abstract class UploadChangesResponse {
+
+ /**
+ * timestamp/ID that can be used for requesting changes since this upload.
+ */
+ public final long timestamp;
+
+ public UploadChangesResponse(long timestamp) {
+ this.timestamp = timestamp;
+ }
+}
diff --git a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
index 9aaebc2cc..ba30c0ef6 100644
--- a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -39,8 +39,6 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
- public static GpodnetCallbacks gpodnetCallbacks;
-
public static DBTasksCallbacks dbTasksCallbacks;
public static CastCallbacks castCallbacks;