diff --git a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java index 161393b8a..587dd38c8 100644 --- a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java +++ b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java @@ -5,10 +5,11 @@ import java.util.Arrays; import java.util.List; import androidx.test.runner.AndroidJUnit4; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -30,7 +31,7 @@ public class GPodnetServiceTest { @Before public void setUp() { - service = new GpodnetService(); + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST); } private void authenticate() throws GpodnetServiceException { @@ -42,7 +43,7 @@ public class GPodnetServiceTest { authenticate(); ArrayList l = new ArrayList<>(); l.add("http://bitsundso.de/feed"); - service.uploadSubscriptions(USER, "radio", l); + service.uploadSubscriptions("radio", l); } @Test @@ -51,7 +52,7 @@ public class GPodnetServiceTest { ArrayList l = new ArrayList<>(); l.add("http://bitsundso.de/feed"); l.add("http://gamesundso.de/feed"); - service.uploadSubscriptions(USER, "radio", l); + service.uploadSubscriptions( "radio", l); } @Test @@ -61,41 +62,40 @@ public class GPodnetServiceTest { List subscriptions = Arrays.asList(URLS[0], URLS[1]); List removed = singletonList(URLS[0]); List added = Arrays.asList(URLS[2], URLS[3]); - service.uploadSubscriptions(USER, "radio", subscriptions); - service.uploadChanges(USER, "radio", added, removed); + service.uploadSubscriptions("radio", subscriptions); + service.uploadChanges("radio", added, removed); } @Test public void testGetSubscriptionChanges() throws GpodnetServiceException { authenticate(); - service.getSubscriptionChanges(USER, "radio", 1362322610L); + service.getSubscriptionChanges("radio", 1362322610L); } @Test public void testGetSubscriptionsOfUser() throws GpodnetServiceException { authenticate(); - service.getSubscriptionsOfUser(USER); + service.getSubscriptionsOfUser(); } @Test public void testGetSubscriptionsOfDevice() throws GpodnetServiceException { authenticate(); - service.getSubscriptionsOfDevice(USER, "radio"); + service.getSubscriptionsOfDevice("radio"); } @Test public void testConfigureDevices() throws GpodnetServiceException { authenticate(); - service.configureDevice(USER, "foo", "This is an updated caption", - GpodnetDevice.DeviceType.LAPTOP); + service.configureDevice("foo", "This is an updated caption", GpodnetDevice.DeviceType.LAPTOP); } @Test public void testGetDevices() throws GpodnetServiceException { authenticate(); - service.getDevices(USER); + service.getDevices(); } @Test diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java index 224b8d182..ea97fc0fd 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java @@ -30,12 +30,13 @@ import java.util.regex.Pattern; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice; 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.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice; /** * Guides the user through the authentication process @@ -69,7 +70,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.gpodnetauth_activity); - service = new GpodnetService(); + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); viewFlipper = findViewById(R.id.viewflipper); LayoutInflater inflater = (LayoutInflater) @@ -85,14 +86,6 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { advance(); } - @Override - protected void onDestroy() { - super.onDestroy(); - if (service != null) { - service.shutdown(); - } - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -221,7 +214,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { @Override protected List doInBackground(GpodnetService... params) { try { - return params[0].getDevices(username); + return params[0].getDevices(); } catch (GpodnetServiceException e) { e.printStackTrace(); return null; @@ -268,7 +261,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { @Override protected GpodnetDevice doInBackground(GpodnetService... params) { try { - params[0].configureDevice(username, deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE); + params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE); return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0); } catch (GpodnetServiceException e) { e.printStackTrace(); @@ -349,7 +342,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { final Button back = view.findViewById(R.id.butGoMainscreen); sync.setOnClickListener(v -> { - GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this); + SyncService.sync(GpodnetAuthenticationActivity.this); finish(); }); back.setOnClickListener(v -> { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java index 3edc23e37..25fc0a05c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java @@ -13,13 +13,14 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; import org.apache.commons.lang3.StringUtils; -import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; + +import java.util.List; /** * Adapter for displaying a list of GPodnetPodcast-Objects. diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java index 52fca792e..698e43145 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/TagListAdapter.java @@ -7,10 +7,10 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; -import java.util.List; - import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag; + +import java.util.List; /** * Adapter for displaying a list of GPodnetPodcast-Objects. diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 3dd7c350d..6e584d34f 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -14,7 +14,6 @@ class ClientConfigurator { ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME; ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); - ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl(); ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); ClientConfig.castCallbacks = new CastCallbackImpl(); diff --git a/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java deleted file mode 100644 index 5f8da6894..000000000 --- a/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.danoeh.antennapod.config; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.GpodnetCallbacks; - - -public class GpodnetCallbacksImpl implements GpodnetCallbacks { - @Override - public boolean gpodnetEnabled() { - return true; - } - - @Override - public PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context) { - return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java index 17668586b..8119dffcb 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java @@ -10,8 +10,8 @@ import android.widget.EditText; import android.widget.LinearLayout; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; /** * Creates a dialog that lets the user change the hostname for the gpodder.net service. diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java index 6e5debb38..4fbac19ff 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java @@ -1,8 +1,10 @@ package de.danoeh.antennapod.discovery; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -14,9 +16,9 @@ import java.util.List; public class GpodnetPodcastSearcher implements PodcastSearcher { public Single> search(String query) { return Single.create((SingleOnSubscribe>) subscriber -> { - GpodnetService service = null; try { - service = new GpodnetService(); + GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), + GpodnetPreferences.getHostname()); List gpodnetPodcasts = service.searchPodcasts(query, 0); List results = new ArrayList<>(); for (GpodnetPodcast podcast : gpodnetPodcasts) { @@ -26,10 +28,6 @@ public class GpodnetPodcastSearcher implements PodcastSearcher { } catch (GpodnetServiceException e) { e.printStackTrace(); subscriber.onError(e); - } finally { - if (service != null) { - service.shutdown(); - } } }) .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java index 481c232c8..0f0c864b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.discovery; import androidx.annotation.Nullable; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; import de.mfietz.fyydlin.SearchHit; import org.json.JSONArray; import org.json.JSONException; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index 3fca43938..915abdb0d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -27,10 +27,11 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; /** * Displays a list of GPodnetPodcast-Objects in a GridView @@ -117,16 +118,12 @@ public abstract class PodcastListFragment extends Fragment { protected List doInBackground(Void... params) { GpodnetService service = null; try { - service = new GpodnetService(); + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); return loadPodcastData(service); } catch (GpodnetServiceException e) { exception = e; e.printStackTrace(); return null; - } finally { - if (service != null) { - service.shutdown(); - } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastTopListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastTopListFragment.java index 4f963756c..64261493d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastTopListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastTopListFragment.java @@ -1,16 +1,12 @@ package de.danoeh.antennapod.fragment.gpodnet; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; + import java.util.List; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; - -/** - * - */ public class PodcastTopListFragment extends PodcastListFragment { - private static final String TAG = "PodcastTopListFragment"; private static final int PODCAST_COUNT = 50; @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java index ffe69aa9a..80f1a6ae0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java @@ -7,15 +7,14 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; import org.apache.commons.lang3.Validate; -import java.util.List; - import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.menuhandler.MenuItemUtils; + +import java.util.List; /** * Performs a search on the gpodder.net directory and displays the results. diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java index 1b1b61efb..41b99cdfc 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java @@ -3,10 +3,10 @@ package de.danoeh.antennapod.fragment.gpodnet; import java.util.Collections; import java.util.List; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; /** * Displays suggestions from gpodder.net diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java index 92cd4ca84..0ce38656a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java @@ -3,15 +3,15 @@ package de.danoeh.antennapod.fragment.gpodnet; import android.os.Bundle; import androidx.annotation.Nullable; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag; import org.apache.commons.lang3.Validate; -import java.util.List; - import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; + +import java.util.List; /** * Shows all podcasts from gpodder.net that belong to a specific tag. diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index a8b6c2976..613218a91 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -13,15 +13,16 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; -import java.util.List; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; -import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag; + +import java.util.List; public class TagListFragment extends ListFragment { private static final int COUNT = 50; @@ -91,15 +92,13 @@ public class TagListFragment extends ListFragment { @Override protected List doInBackground(Void... params) { - GpodnetService service = new GpodnetService(); + GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); try { return service.getTopTags(COUNT); } catch (GpodnetServiceException e) { e.printStackTrace(); exception = e; return null; - } finally { - service.shutdown(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java index 271b31fad..5b622b5c8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java @@ -11,7 +11,7 @@ import android.widget.Toast; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.service.GpodnetSyncService; +import de.danoeh.antennapod.core.sync.SyncService; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; @@ -51,10 +51,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { private final SharedPreferences.OnSharedPreferenceChangeListener gpoddernetListener = (sharedPreferences, key) -> { - if (GpodnetPreferences.PREF_LAST_SYNC_ATTEMPT_TIMESTAMP.equals(key)) { - updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(), - GpodnetPreferences.getLastSyncAttemptTimestamp()); - } + //if (GpodnetPreferences.PREF_LAST_SYNC_ATTEMPT_TIMESTAMP.equals(key)) { + // updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(), + // GpodnetPreferences.getLastSyncAttemptTimestamp()); + //} }; private void setupGpodderScreen() { @@ -75,21 +75,13 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { return true; }); findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> { - GpodnetSyncService.sendSyncIntent(getActivity().getApplicationContext()); - Toast toast = Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, - Toast.LENGTH_SHORT); - toast.show(); + SyncService.sync(getActivity().getApplicationContext()); + Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT).show(); return true; }); findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { - GpodnetPreferences.setLastSubscriptionSyncTimestamp(0L); - GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(0L); - GpodnetPreferences.setLastSyncAttempt(false, 0); - updateLastGpodnetSyncReport(false, 0); - GpodnetSyncService.sendSyncIntent(getActivity().getApplicationContext()); - Toast toast = Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, - Toast.LENGTH_SHORT); - toast.show(); + SyncService.fullSync(getContext()); + Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT).show(); return true; }); findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> { @@ -119,8 +111,8 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { String summary = String.format(format, GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID()); findPreference(PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary)); - updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(), - GpodnetPreferences.getLastSyncAttemptTimestamp()); + //updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(), + // GpodnetPreferences.getLastSyncAttemptTimestamp()); } else { findPreference(PREF_GPODNET_LOGOUT).setSummary(null); updateLastGpodnetSyncReport(false, 0); diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index edd594385..c9c7b6190 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -12,13 +12,13 @@ import android.view.MenuItem; import de.danoeh.antennapod.R; 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.gpoddernet.model.GpodnetEpisodeAction.Action; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.model.EpisodeAction; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; @@ -191,14 +191,13 @@ public class FeedItemMenuHandler { FeedMedia media = selectedItem.getMedia(); // not all items have media, Gpodder only cares about those that do if (media != null) { - GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY) - .currentDeviceId() + EpisodeAction actionPlay = new EpisodeAction.Builder(selectedItem, EpisodeAction.PLAY) .currentTimestamp() .started(media.getDuration() / 1000) .position(media.getDuration() / 1000) .total(media.getDuration() / 1000) .build(); - GpodnetPreferences.enqueueEpisodeAction(actionPlay); + SyncService.enqueueEpisodeAction(context, actionPlay); } } break; @@ -206,11 +205,10 @@ public class FeedItemMenuHandler { selectedItem.setPlayed(false); DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false); if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) { - GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW) - .currentDeviceId() + EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW) .currentTimestamp() .build(); - GpodnetPreferences.enqueueEpisodeAction(actionNew); + SyncService.enqueueEpisodeAction(context, actionNew); } break; case R.id.add_to_queue_item: 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 @@ + android:enabled="true" + android:exported="false" /> - * 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 addedFeeds; - private static Set removedFeeds; - - private static List 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 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 getAddedFeedsCopy() { - ensurePreferencesLoaded(); - Set copy = new HashSet<>(); - feedListLock.lock(); - copy.addAll(addedFeeds); - feedListLock.unlock(); - return copy; - } - - public static void removeAddedFeeds(Collection removed) { - ensurePreferencesLoaded(); - feedListLock.lock(); - addedFeeds.removeAll(removed); - writePreference(PREF_SYNC_ADDED, addedFeeds); - feedListLock.unlock(); - } - - public static Set getRemovedFeedsCopy() { - ensurePreferencesLoaded(); - Set copy = new HashSet<>(); - feedListLock.lock(); - copy.addAll(removedFeeds); - feedListLock.unlock(); - return copy; - } - - public static void removeRemovedFeeds(Collection 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 getQueuedEpisodeActions() { - ensurePreferencesLoaded(); - List copy = new ArrayList<>(); - feedListLock.lock(); - copy.addAll(queuedEpisodeActions); - feedListLock.unlock(); - return copy; - } - - public static void removeQueuedEpisodeActions(Collection 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 readListFromString(String s) { - Set result = new HashSet<>(); - Collections.addAll(result, s.split(" ")); - return result; - } - - private static String writeListToString(Collection c) { - StringBuilder result = new StringBuilder(); - for (String item : c) { - result.append(item); - result.append(" "); - } - return result.toString().trim(); - } - - private static List readEpisodeActionsFromString(String s) { - String[] lines = s.split("\n"); - List 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 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 localSubscriptions = DBReader.getFeedListDownloadUrls(); - Collection localAdded = GpodnetPreferences.getAddedFeedsCopy(); - Collection 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 localSubscriptions, - Collection localAdded, - Collection 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 remoteActions = getResponse.getEpisodeActions(); - - List 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 localActions, - List remoteActions) { - if(remoteActions.size() == 0) { - return; - } - Map, GpodnetEpisodeAction> localMostRecentPlayAction = new ArrayMap<>(); - for(GpodnetEpisodeAction action : localActions) { - Pair 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, 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 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 getQueuedEpisodeActions() { + ArrayList 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 getQueuedRemovedFeeds() { + ArrayList 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 getQueuedAddedFeeds() { + ArrayList 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 localSubscriptions = DBReader.getFeedListDownloadUrls(); + SubscriptionChanges subscriptionChanges = syncServiceImpl.getSubscriptionChanges(lastSync); + long newTimeStamp = subscriptionChanges.getTimestamp(); + + List queuedRemovedFeeds = getQueuedRemovedFeeds(); + List 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 remoteActions = getResponse.getEpisodeActions(); + processEpisodeActions(remoteActions); + + // upload local actions + List 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 remoteActions) { + if (remoteActions.size() == 0) { + return; + } + Map, EpisodeAction> localMostRecentPlayAction = new ArrayMap<>(); + for (EpisodeAction action : getQueuedEpisodeActions()) { + Pair 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, 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 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 similarity index 64% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java rename to 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 getTopTags(int count) - throws GpodnetServiceException { + public List 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 tagList = new ArrayList<>( - jsonTagList.length()); + List 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 getPodcastsForTag(@NonNull GpodnetTag tag, - int count) + public List 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 getPodcastToplist(int count) - throws GpodnetServiceException { - if(count < 1 || count > 100) { + public List 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 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 searchPodcasts(String query, int scaledLogoSize) - throws GpodnetServiceException { + public List 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 { *

* 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 getDevices(@NonNull String username) - throws GpodnetServiceException { + public List 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 { *

* 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 { *

* 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 { *

* 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 { *

* 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 subscriptions) + public void uploadSubscriptions(@NonNull String deviceId, @NonNull List 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 { *

* 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 added, - @NonNull Collection removed) - throws GpodnetServiceException { - + public GpodnetUploadChangesResponse uploadChanges(@NonNull String deviceId, @NonNull Collection added, + @NonNull Collection 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 { *

* 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 { *

* 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 episodeActions) - throws GpodnetServiceException { - - String username = GpodnetPreferences.getUsername(); - + @Override + public UploadChangesResponse uploadEpisodeActions(List 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 readPodcastListFromJSONArray(@NonNull JSONArray array) - throws JSONException { - List result = new ArrayList<>( - array.length()); + private List readPodcastListFromJSONArray(@NonNull JSONArray array) throws JSONException { + List 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 readDeviceListFromJSONArray(@NonNull JSONArray array) - throws JSONException { - List result = new ArrayList<>( - array.length()); + private List readDeviceListFromJSONArray(@NonNull JSONArray array) throws JSONException { + List 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 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 episodeActions = new ArrayList<>(); + List 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 addedFeeds, List 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 similarity index 86% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java rename to 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 similarity index 96% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java rename to 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 similarity index 85% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java rename to 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 updatedUrls; private GpodnetEpisodeActionPostResponse(long timestamp, Map 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 similarity index 96% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java rename to 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 similarity index 96% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java rename to 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 similarity index 73% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java rename to 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 updatedUrls; + public final Map updatedUrls; - private GpodnetUploadChangesResponse(long timestamp, Map updatedUrls) { - this.timestamp = timestamp; + public GpodnetUploadChangesResponse(long timestamp, Map 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 similarity index 62% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java rename to 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,104 +13,65 @@ 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; this.total = builder.total; } - /** - * 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 similarity index 52% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java rename to 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 episodeActions; + private final List episodeActions; private final long timestamp; - public GpodnetEpisodeActionGetResponse(@NonNull List episodeActions, - long timestamp) { + public EpisodeActionChanges(@NonNull List episodeActions, long timestamp) { this.episodeActions = episodeActions; this.timestamp = timestamp; } - public List getEpisodeActions() { + public List 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 addedFeeds, List removedFeeds) throws SyncServiceException; + + EpisodeActionChanges getEpisodeActionChanges(long lastSync) throws SyncServiceException; + + UploadChangesResponse uploadEpisodeActions(List 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 similarity index 64% rename from core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java rename to 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 added; private final List removed; private final long timestamp; - public GpodnetSubscriptionChange(@NonNull List added, - @NonNull List removed, - long timestamp) { + public SubscriptionChanges(@NonNull List added, + @NonNull List 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; diff --git a/settings.gradle b/settings.gradle index f8f9b77af..3bd20b840 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ -include ':app' -include ':core' +include ':app', ':sync', ':core'