Merge pull request #3970 from ByteHamster/extract-gpodder
SyncService refactoring
This commit is contained in:
commit
b195e32c04
@ -143,6 +143,7 @@ dependencies {
|
|||||||
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||||
implementation "androidx.media:media:1.1.0"
|
implementation "androidx.media:media:1.1.0"
|
||||||
|
implementation "android.arch.work:work-runtime:$workManagerVersion"
|
||||||
implementation "com.google.android.material:material:1.0.0"
|
implementation "com.google.android.material:material:1.0.0"
|
||||||
annotationProcessor "androidx.annotation:annotation:1.1.0"
|
annotationProcessor "androidx.annotation:annotation:1.1.0"
|
||||||
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
|
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
|
||||||
|
@ -5,10 +5,11 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
|
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
|
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.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -30,7 +31,7 @@ public class GPodnetServiceTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
service = new GpodnetService();
|
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticate() throws GpodnetServiceException {
|
private void authenticate() throws GpodnetServiceException {
|
||||||
@ -42,7 +43,7 @@ public class GPodnetServiceTest {
|
|||||||
authenticate();
|
authenticate();
|
||||||
ArrayList<String> l = new ArrayList<>();
|
ArrayList<String> l = new ArrayList<>();
|
||||||
l.add("http://bitsundso.de/feed");
|
l.add("http://bitsundso.de/feed");
|
||||||
service.uploadSubscriptions(USER, "radio", l);
|
service.uploadSubscriptions("radio", l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -51,7 +52,7 @@ public class GPodnetServiceTest {
|
|||||||
ArrayList<String> l = new ArrayList<>();
|
ArrayList<String> l = new ArrayList<>();
|
||||||
l.add("http://bitsundso.de/feed");
|
l.add("http://bitsundso.de/feed");
|
||||||
l.add("http://gamesundso.de/feed");
|
l.add("http://gamesundso.de/feed");
|
||||||
service.uploadSubscriptions(USER, "radio", l);
|
service.uploadSubscriptions("radio", l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -61,41 +62,40 @@ public class GPodnetServiceTest {
|
|||||||
List<String> subscriptions = Arrays.asList(URLS[0], URLS[1]);
|
List<String> subscriptions = Arrays.asList(URLS[0], URLS[1]);
|
||||||
List<String> removed = singletonList(URLS[0]);
|
List<String> removed = singletonList(URLS[0]);
|
||||||
List<String> added = Arrays.asList(URLS[2], URLS[3]);
|
List<String> added = Arrays.asList(URLS[2], URLS[3]);
|
||||||
service.uploadSubscriptions(USER, "radio", subscriptions);
|
service.uploadSubscriptions("radio", subscriptions);
|
||||||
service.uploadChanges(USER, "radio", added, removed);
|
service.uploadChanges("radio", added, removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSubscriptionChanges() throws GpodnetServiceException {
|
public void testGetSubscriptionChanges() throws GpodnetServiceException {
|
||||||
authenticate();
|
authenticate();
|
||||||
service.getSubscriptionChanges(USER, "radio", 1362322610L);
|
service.getSubscriptionChanges("radio", 1362322610L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSubscriptionsOfUser()
|
public void testGetSubscriptionsOfUser()
|
||||||
throws GpodnetServiceException {
|
throws GpodnetServiceException {
|
||||||
authenticate();
|
authenticate();
|
||||||
service.getSubscriptionsOfUser(USER);
|
service.getSubscriptionsOfUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSubscriptionsOfDevice()
|
public void testGetSubscriptionsOfDevice()
|
||||||
throws GpodnetServiceException {
|
throws GpodnetServiceException {
|
||||||
authenticate();
|
authenticate();
|
||||||
service.getSubscriptionsOfDevice(USER, "radio");
|
service.getSubscriptionsOfDevice("radio");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConfigureDevices() throws GpodnetServiceException {
|
public void testConfigureDevices() throws GpodnetServiceException {
|
||||||
authenticate();
|
authenticate();
|
||||||
service.configureDevice(USER, "foo", "This is an updated caption",
|
service.configureDevice("foo", "This is an updated caption", GpodnetDevice.DeviceType.LAPTOP);
|
||||||
GpodnetDevice.DeviceType.LAPTOP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDevices() throws GpodnetServiceException {
|
public void testGetDevices() throws GpodnetServiceException {
|
||||||
authenticate();
|
authenticate();
|
||||||
service.getDevices(USER);
|
service.getDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -30,12 +30,13 @@ import java.util.regex.Pattern;
|
|||||||
import de.danoeh.antennapod.BuildConfig;
|
import de.danoeh.antennapod.BuildConfig;
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
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.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
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
|
* Guides the user through the authentication process
|
||||||
@ -69,7 +70,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
|||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
setContentView(R.layout.gpodnetauth_activity);
|
setContentView(R.layout.gpodnetauth_activity);
|
||||||
service = new GpodnetService();
|
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
|
||||||
|
|
||||||
viewFlipper = findViewById(R.id.viewflipper);
|
viewFlipper = findViewById(R.id.viewflipper);
|
||||||
LayoutInflater inflater = (LayoutInflater)
|
LayoutInflater inflater = (LayoutInflater)
|
||||||
@ -85,14 +86,6 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
|||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (service != null) {
|
|
||||||
service.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
@ -221,7 +214,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
|
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
|
||||||
try {
|
try {
|
||||||
return params[0].getDevices(username);
|
return params[0].getDevices();
|
||||||
} catch (GpodnetServiceException e) {
|
} catch (GpodnetServiceException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@ -268,7 +261,7 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected GpodnetDevice doInBackground(GpodnetService... params) {
|
protected GpodnetDevice doInBackground(GpodnetService... params) {
|
||||||
try {
|
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);
|
return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
|
||||||
} catch (GpodnetServiceException e) {
|
} catch (GpodnetServiceException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -349,8 +342,8 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
|||||||
final Button back = view.findViewById(R.id.butGoMainscreen);
|
final Button back = view.findViewById(R.id.butGoMainscreen);
|
||||||
|
|
||||||
sync.setOnClickListener(v -> {
|
sync.setOnClickListener(v -> {
|
||||||
GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this);
|
|
||||||
finish();
|
finish();
|
||||||
|
SyncService.sync(getApplicationContext());
|
||||||
});
|
});
|
||||||
back.setOnClickListener(v -> {
|
back.setOnClickListener(v -> {
|
||||||
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
|
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
|
||||||
|
@ -13,13 +13,14 @@ import com.bumptech.glide.Glide;
|
|||||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
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.
|
* Adapter for displaying a list of GPodnetPodcast-Objects.
|
||||||
|
@ -7,10 +7,10 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
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.
|
* Adapter for displaying a list of GPodnetPodcast-Objects.
|
||||||
|
@ -14,7 +14,6 @@ class ClientConfigurator {
|
|||||||
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
|
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
|
||||||
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
|
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
|
||||||
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
|
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
|
||||||
ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl();
|
|
||||||
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
||||||
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
|
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
|
||||||
ClientConfig.castCallbacks = new CastCallbackImpl();
|
ClientConfig.castCallbacks = new CastCallbackImpl();
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,8 +10,8 @@ import android.widget.EditText;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
|
||||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
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.
|
* Creates a dialog that lets the user change the hostname for the gpodder.net service.
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package de.danoeh.antennapod.discovery;
|
package de.danoeh.antennapod.discovery;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
|
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.Single;
|
||||||
import io.reactivex.SingleOnSubscribe;
|
import io.reactivex.SingleOnSubscribe;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
@ -14,9 +16,9 @@ import java.util.List;
|
|||||||
public class GpodnetPodcastSearcher implements PodcastSearcher {
|
public class GpodnetPodcastSearcher implements PodcastSearcher {
|
||||||
public Single<List<PodcastSearchResult>> search(String query) {
|
public Single<List<PodcastSearchResult>> search(String query) {
|
||||||
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
||||||
GpodnetService service = null;
|
|
||||||
try {
|
try {
|
||||||
service = new GpodnetService();
|
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||||
|
GpodnetPreferences.getHostname());
|
||||||
List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
|
List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
|
||||||
List<PodcastSearchResult> results = new ArrayList<>();
|
List<PodcastSearchResult> results = new ArrayList<>();
|
||||||
for (GpodnetPodcast podcast : gpodnetPodcasts) {
|
for (GpodnetPodcast podcast : gpodnetPodcasts) {
|
||||||
@ -26,10 +28,6 @@ public class GpodnetPodcastSearcher implements PodcastSearcher {
|
|||||||
} catch (GpodnetServiceException e) {
|
} catch (GpodnetServiceException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
subscriber.onError(e);
|
subscriber.onError(e);
|
||||||
} finally {
|
|
||||||
if (service != null) {
|
|
||||||
service.shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package de.danoeh.antennapod.discovery;
|
package de.danoeh.antennapod.discovery;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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 de.mfietz.fyydlin.SearchHit;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -27,10 +27,11 @@ import de.danoeh.antennapod.R;
|
|||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||||
import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter;
|
import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
|
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
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
|
* Displays a list of GPodnetPodcast-Objects in a GridView
|
||||||
@ -115,18 +116,14 @@ public abstract class PodcastListFragment extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<GpodnetPodcast> doInBackground(Void... params) {
|
protected List<GpodnetPodcast> doInBackground(Void... params) {
|
||||||
GpodnetService service = null;
|
|
||||||
try {
|
try {
|
||||||
service = new GpodnetService();
|
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||||
|
GpodnetPreferences.getHostname());
|
||||||
return loadPodcastData(service);
|
return loadPodcastData(service);
|
||||||
} catch (GpodnetServiceException e) {
|
} catch (GpodnetServiceException e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
|
||||||
if (service != null) {
|
|
||||||
service.shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
package de.danoeh.antennapod.fragment.gpodnet;
|
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 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 {
|
public class PodcastTopListFragment extends PodcastListFragment {
|
||||||
private static final String TAG = "PodcastTopListFragment";
|
|
||||||
private static final int PODCAST_COUNT = 50;
|
private static final int PODCAST_COUNT = 50;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,15 +7,14 @@ import android.view.Menu;
|
|||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
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 org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import java.util.List;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
|
|
||||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a search on the gpodder.net directory and displays the results.
|
* Performs a search on the gpodder.net directory and displays the results.
|
||||||
|
@ -3,10 +3,10 @@ package de.danoeh.antennapod.fragment.gpodnet;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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.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
|
* Displays suggestions from gpodder.net
|
||||||
|
@ -3,15 +3,15 @@ package de.danoeh.antennapod.fragment.gpodnet;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
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 org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import java.util.List;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows all podcasts from gpodder.net that belong to a specific tag.
|
* Shows all podcasts from gpodder.net that belong to a specific tag.
|
||||||
|
@ -13,15 +13,16 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
|
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
|
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
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 {
|
public class TagListFragment extends ListFragment {
|
||||||
private static final int COUNT = 50;
|
private static final int COUNT = 50;
|
||||||
@ -91,15 +92,14 @@ public class TagListFragment extends ListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<GpodnetTag> doInBackground(Void... params) {
|
protected List<GpodnetTag> doInBackground(Void... params) {
|
||||||
GpodnetService service = new GpodnetService();
|
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||||
|
GpodnetPreferences.getHostname());
|
||||||
try {
|
try {
|
||||||
return service.getTopTags(COUNT);
|
return service.getTopTags(COUNT);
|
||||||
} catch (GpodnetServiceException e) {
|
} catch (GpodnetServiceException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
exception = e;
|
exception = e;
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
|
||||||
service.shutdown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,15 @@ import android.text.format.DateUtils;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||||
|
import de.danoeh.antennapod.core.event.SyncServiceEvent;
|
||||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
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.AuthenticationDialog;
|
||||||
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
|
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
|
|
||||||
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
|
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
|
||||||
@ -34,28 +39,30 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label);
|
((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
GpodnetPreferences.registerOnSharedPreferenceChangeListener(gpoddernetListener);
|
|
||||||
updateGpodnetPreferenceScreen();
|
updateGpodnetPreferenceScreen();
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onStop() {
|
||||||
super.onPause();
|
super.onStop();
|
||||||
GpodnetPreferences.unregisterOnSharedPreferenceChangeListener(gpoddernetListener);
|
EventBus.getDefault().unregister(this);
|
||||||
|
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener gpoddernetListener =
|
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||||
(sharedPreferences, key) -> {
|
public void syncStatusChanged(SyncServiceEvent event) {
|
||||||
if (GpodnetPreferences.PREF_LAST_SYNC_ATTEMPT_TIMESTAMP.equals(key)) {
|
if (!GpodnetPreferences.loggedIn()) {
|
||||||
updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(),
|
return;
|
||||||
GpodnetPreferences.getLastSyncAttemptTimestamp());
|
}
|
||||||
}
|
if (event.getMessageResId() == R.string.sync_status_error
|
||||||
};
|
|| event.getMessageResId() == R.string.sync_status_success) {
|
||||||
|
updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()),
|
||||||
|
SyncService.getLastSyncAttempt(getContext()));
|
||||||
|
} else {
|
||||||
|
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setupGpodderScreen() {
|
private void setupGpodderScreen() {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
@ -75,35 +82,25 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> {
|
findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> {
|
||||||
GpodnetSyncService.sendSyncIntent(getActivity().getApplicationContext());
|
SyncService.syncImmediately(getActivity().getApplicationContext());
|
||||||
Toast toast = Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started,
|
return true;
|
||||||
Toast.LENGTH_SHORT);
|
});
|
||||||
toast.show();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> {
|
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> {
|
||||||
GpodnetPreferences.setLastSubscriptionSyncTimestamp(0L);
|
SyncService.fullSync(getContext());
|
||||||
GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(0L);
|
return true;
|
||||||
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();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> {
|
findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> {
|
||||||
GpodnetPreferences.logout();
|
GpodnetPreferences.logout();
|
||||||
Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT);
|
Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT);
|
||||||
toast.show();
|
toast.show();
|
||||||
updateGpodnetPreferenceScreen();
|
updateGpodnetPreferenceScreen();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
|
findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
|
||||||
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
|
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
|
||||||
dialog -> updateGpodnetPreferenceScreen());
|
dialog -> updateGpodnetPreferenceScreen());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGpodnetPreferenceScreen() {
|
private void updateGpodnetPreferenceScreen() {
|
||||||
@ -114,35 +111,24 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||||||
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setEnabled(loggedIn);
|
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setEnabled(loggedIn);
|
||||||
findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn);
|
findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn);
|
||||||
findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(loggedIn);
|
findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(loggedIn);
|
||||||
if(loggedIn) {
|
if (loggedIn) {
|
||||||
String format = getActivity().getString(R.string.pref_gpodnet_login_status);
|
String format = getActivity().getString(R.string.pref_gpodnet_login_status);
|
||||||
String summary = String.format(format, GpodnetPreferences.getUsername(),
|
String summary = String.format(format, GpodnetPreferences.getUsername(),
|
||||||
GpodnetPreferences.getDeviceID());
|
GpodnetPreferences.getDeviceID());
|
||||||
findPreference(PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary));
|
findPreference(PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary));
|
||||||
updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(),
|
updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()),
|
||||||
GpodnetPreferences.getLastSyncAttemptTimestamp());
|
SyncService.getLastSyncAttempt(getContext()));
|
||||||
} else {
|
} else {
|
||||||
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
|
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
|
||||||
updateLastGpodnetSyncReport(false, 0);
|
|
||||||
}
|
}
|
||||||
findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
|
findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
|
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
|
||||||
Preference sync = findPreference(PREF_GPODNET_SYNC);
|
String status = String.format("%1$s (%2$s)", getString(successful
|
||||||
if (lastTime != 0) {
|
? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed),
|
||||||
sync.setSummary(getActivity().getString(R.string.pref_gpodnet_sync_changes_sum) + "\n" +
|
DateUtils.getRelativeDateTimeString(getContext(),
|
||||||
getActivity().getString(R.string.pref_gpodnet_sync_sum_last_sync_line,
|
lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME));
|
||||||
getActivity().getString(successful ?
|
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status);
|
||||||
R.string.gpodnetsync_pref_report_successful :
|
|
||||||
R.string.gpodnetsync_pref_report_failed),
|
|
||||||
DateUtils.getRelativeDateTimeString(getActivity(),
|
|
||||||
lastTime,
|
|
||||||
DateUtils.MINUTE_IN_MILLIS,
|
|
||||||
DateUtils.WEEK_IN_MILLIS,
|
|
||||||
DateUtils.FORMAT_SHOW_TIME)));
|
|
||||||
} else {
|
|
||||||
sync.setSummary(getActivity().getString(R.string.pref_gpodnet_sync_changes_sum));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ import android.view.MenuItem;
|
|||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.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.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
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.FeedItemUtil;
|
||||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||||
import de.danoeh.antennapod.core.util.ShareUtils;
|
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||||
@ -191,14 +191,13 @@ public class FeedItemMenuHandler {
|
|||||||
FeedMedia media = selectedItem.getMedia();
|
FeedMedia media = selectedItem.getMedia();
|
||||||
// not all items have media, Gpodder only cares about those that do
|
// not all items have media, Gpodder only cares about those that do
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY)
|
EpisodeAction actionPlay = new EpisodeAction.Builder(selectedItem, EpisodeAction.PLAY)
|
||||||
.currentDeviceId()
|
|
||||||
.currentTimestamp()
|
.currentTimestamp()
|
||||||
.started(media.getDuration() / 1000)
|
.started(media.getDuration() / 1000)
|
||||||
.position(media.getDuration() / 1000)
|
.position(media.getDuration() / 1000)
|
||||||
.total(media.getDuration() / 1000)
|
.total(media.getDuration() / 1000)
|
||||||
.build();
|
.build();
|
||||||
GpodnetPreferences.enqueueEpisodeAction(actionPlay);
|
SyncService.enqueueEpisodeAction(context, actionPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -206,11 +205,10 @@ public class FeedItemMenuHandler {
|
|||||||
selectedItem.setPlayed(false);
|
selectedItem.setPlayed(false);
|
||||||
DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
|
DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
|
||||||
if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) {
|
if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) {
|
||||||
GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW)
|
EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
|
||||||
.currentDeviceId()
|
|
||||||
.currentTimestamp()
|
.currentTimestamp()
|
||||||
.build();
|
.build();
|
||||||
GpodnetPreferences.enqueueEpisodeAction(actionNew);
|
SyncService.enqueueEpisodeAction(context, actionNew);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.add_to_queue_item:
|
case R.id.add_to_queue_item:
|
||||||
|
@ -1,28 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@id/txtvTitle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@id/icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_login_title"
|
android:src="@drawable/gpodder_icon" />
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
style="@style/AntennaPod.TextView.Heading"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/txtvDescription"
|
android:id="@id/txtvDescription"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_login_descr"
|
android:text="@string/gpodnetauth_login_descr"
|
||||||
android:layout_below="@id/txtvTitle"
|
android:layout_below="@id/icon"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorPrimary"/>
|
||||||
android:layout_margin="16dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etxtUsername"
|
android:id="@+id/etxtUsername"
|
||||||
@ -30,7 +27,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/username_label"
|
android:hint="@string/username_label"
|
||||||
android:layout_below="@id/txtvDescription"
|
android:layout_below="@id/txtvDescription"
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:cursorVisible="true"
|
android:cursorVisible="true"
|
||||||
@ -46,7 +42,6 @@
|
|||||||
android:hint="@string/password_label"
|
android:hint="@string/password_label"
|
||||||
android:layout_below="@id/etxtUsername"
|
android:layout_below="@id/etxtUsername"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:cursorVisible="true"
|
android:cursorVisible="true"
|
||||||
@ -60,8 +55,7 @@
|
|||||||
android:layout_below="@id/etxtPassword"
|
android:layout_below="@id/etxtPassword"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:text="@string/gpodnetauth_login_butLabel"
|
android:text="@string/gpodnetauth_login_butLabel"/>
|
||||||
android:layout_margin="8dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/txtvError"
|
android:id="@+id/txtvError"
|
||||||
@ -94,8 +88,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/gpodnetauth_login_register"
|
android:text="@string/gpodnetauth_login_register"
|
||||||
android:autoLink="web"
|
android:autoLink="web"
|
||||||
android:layout_below="@id/butLogin"/>
|
android:layout_below="@id/butLogin"/>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/txtvTitle"
|
android:id="@+id/txtvTitle"
|
||||||
@ -11,7 +12,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_device_title"
|
android:text="@string/gpodnetauth_device_title"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
style="@style/AntennaPod.TextView.Heading"/>
|
style="@style/AntennaPod.TextView.Heading"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -21,8 +22,7 @@
|
|||||||
android:text="@string/gpodnetauth_device_descr"
|
android:text="@string/gpodnetauth_device_descr"
|
||||||
android:layout_below="@id/txtvTitle"
|
android:layout_below="@id/txtvTitle"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorPrimary"/>
|
||||||
android:layout_margin="16dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etxtCaption"
|
android:id="@+id/etxtCaption"
|
||||||
@ -30,7 +30,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/gpodnetauth_device_caption"
|
android:hint="@string/gpodnetauth_device_caption"
|
||||||
android:layout_below="@id/txtvDescription"
|
android:layout_below="@id/txtvDescription"
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:imeOptions="flagNoFullscreen"/>
|
android:imeOptions="flagNoFullscreen"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -39,29 +38,21 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_device_deviceID"
|
android:text="@string/gpodnetauth_device_deviceID"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:layout_below="@id/etxtCaption"/>
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:layout_alignTop="@+id/etxtDeviceID"
|
|
||||||
android:layout_alignLeft="@+id/etxtCaption"
|
|
||||||
android:layout_alignStart="@+id/etxtCaption"/>
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etxtDeviceID"
|
android:id="@+id/etxtDeviceID"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/etxtCaption"
|
android:layout_below="@id/txtvDeviceID"
|
||||||
android:layout_toRightOf="@id/txtvDeviceID"
|
|
||||||
android:layout_toEndOf="@id/txtvDeviceID"
|
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:imeOptions="flagNoFullscreen"/>
|
android:imeOptions="flagNoFullscreen"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/butCreateNewDevice"
|
android:id="@+id/butCreateNewDevice"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_below="@id/etxtDeviceID"
|
android:layout_below="@id/etxtDeviceID"
|
||||||
@ -77,7 +68,6 @@
|
|||||||
android:layout_toLeftOf="@id/butCreateNewDevice"
|
android:layout_toLeftOf="@id/butCreateNewDevice"
|
||||||
android:layout_toStartOf="@id/butCreateNewDevice"
|
android:layout_toStartOf="@id/butCreateNewDevice"
|
||||||
android:textColor="@color/download_failed_red"
|
android:textColor="@color/download_failed_red"
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:textSize="@dimen/text_size_small"
|
android:textSize="@dimen/text_size_small"
|
||||||
tools:text="Error message"
|
tools:text="Error message"
|
||||||
tools:background="@android:color/holo_green_dark" />
|
tools:background="@android:color/holo_green_dark" />
|
||||||
@ -100,30 +90,25 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_device_chooseExistingDevice"
|
android:text="@string/gpodnetauth_device_chooseExistingDevice"
|
||||||
android:layout_below="@id/butCreateNewDevice"
|
android:layout_below="@id/butCreateNewDevice"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:layout_marginTop="32dp"/>
|
||||||
android:layout_margin="16dp"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/butChooseExistingDevice"
|
android:id="@+id/butChooseExistingDevice"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/gpodnetauth_device_butChoose"
|
android:text="@string/gpodnetauth_device_butChoose"
|
||||||
android:layout_below="@+id/spinnerChooseDevice"
|
android:layout_below="@+id/spinnerChooseDevice"
|
||||||
android:layout_alignLeft="@+id/butCreateNewDevice"
|
|
||||||
android:layout_alignStart="@+id/butCreateNewDevice"
|
|
||||||
android:layout_alignRight="@+id/butCreateNewDevice"
|
|
||||||
android:layout_alignEnd="@+id/butCreateNewDevice"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerChooseDevice"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/txtvChooseExistingDevice"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"/>
|
android:layout_alignParentEnd="true"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerChooseDevice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/txtvChooseExistingDevice"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -1,16 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:id="@id/icon"
|
||||||
android:layout_height="match_parent">
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/gpodder_icon" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/txtvTitle"
|
android:id="@+id/txtvTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/icon"
|
||||||
android:text="@string/gpodnetauth_finish_title"
|
android:text="@string/gpodnetauth_finish_title"
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
style="@style/AntennaPod.TextView.Heading"/>
|
style="@style/AntennaPod.TextView.Heading"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -20,15 +26,14 @@
|
|||||||
android:text="@string/gpodnetauth_finish_descr"
|
android:text="@string/gpodnetauth_finish_descr"
|
||||||
android:layout_below="@id/txtvTitle"
|
android:layout_below="@id/txtvTitle"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
android:layout_margin="16dp"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/butSyncNow"
|
android:id="@+id/butSyncNow"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/txtvDescription"
|
android:layout_below="@id/txtvDescription"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/gpodnetauth_finish_butsyncnow"/>
|
android:text="@string/gpodnetauth_finish_butsyncnow"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -36,7 +41,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/butSyncNow"
|
android:layout_below="@id/butSyncNow"
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/gpodnetauth_finish_butgomainscreen"/>
|
android:text="@string/gpodnetauth_finish_butgomainscreen"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -1,10 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="prefGpodderSettings"
|
android:key="prefGpodderSettings"
|
||||||
android:title="@string/gpodnet_main_label"
|
android:title="@string/gpodnet_main_label"
|
||||||
|
app:icon="@drawable/gpodder_icon"
|
||||||
android:summary="@string/gpodnet_summary" />
|
android:summary="@string/gpodnet_summary" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@ -176,7 +176,6 @@
|
|||||||
<property name="ignoreFinal" value="false"/>
|
<property name="ignoreFinal" value="false"/>
|
||||||
<property name="allowedAbbreviationLength" value="1"/>
|
<property name="allowedAbbreviationLength" value="1"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="OverloadMethodsDeclarationOrder"/>
|
|
||||||
<module name="VariableDeclarationUsageDistance"/>
|
<module name="VariableDeclarationUsageDistance"/>
|
||||||
<module name="MethodParamPad"/>
|
<module name="MethodParamPad"/>
|
||||||
<module name="WhitespaceAfter"/>
|
<module name="WhitespaceAfter"/>
|
||||||
|
@ -30,8 +30,6 @@ public class ClientConfig {
|
|||||||
|
|
||||||
public static PlaybackServiceCallbacks playbackServiceCallbacks;
|
public static PlaybackServiceCallbacks playbackServiceCallbacks;
|
||||||
|
|
||||||
public static GpodnetCallbacks gpodnetCallbacks;
|
|
||||||
|
|
||||||
public static DBTasksCallbacks dbTasksCallbacks;
|
public static DBTasksCallbacks dbTasksCallbacks;
|
||||||
|
|
||||||
public static CastCallbacks castCallbacks;
|
public static CastCallbacks castCallbacks;
|
||||||
|
@ -25,10 +25,6 @@
|
|||||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service
|
|
||||||
android:name=".service.GpodnetSyncService"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
|
||||||
android:enabled="true" />
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.MediaButtonReceiver"
|
android:name=".receiver.MediaButtonReceiver"
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package de.danoeh.antennapod.core;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callbacks related to the gpodder.net integration of the core module
|
|
||||||
*/
|
|
||||||
public interface GpodnetCallbacks {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if true if the gpodder.net integration should be activated,
|
|
||||||
* false otherwise.
|
|
||||||
*/
|
|
||||||
boolean gpodnetEnabled();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a PendingIntent for the error notification of the GpodnetSyncService.
|
|
||||||
* <p/>
|
|
||||||
* What the PendingIntent does may be implementation-specific.
|
|
||||||
*
|
|
||||||
* @return A PendingIntent for the notification or null if gpodder.net integration
|
|
||||||
* has been disabled (i.e. gpodnetEnabled() == false).
|
|
||||||
*/
|
|
||||||
PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context);
|
|
||||||
}
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package de.danoeh.antennapod.core.event;
|
||||||
|
|
||||||
|
public class SyncServiceEvent {
|
||||||
|
private final int messageResId;
|
||||||
|
|
||||||
|
public SyncServiceEvent(int messageResId) {
|
||||||
|
this.messageResId = messageResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageResId() {
|
||||||
|
return messageResId;
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,6 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
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.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
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.storage.PodDBAdapter;
|
||||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
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 {
|
public class FeedMedia extends FeedFile implements Playable {
|
||||||
private static final String TAG = "FeedMedia";
|
private static final String TAG = "FeedMedia";
|
||||||
@ -502,17 +503,14 @@ public class FeedMedia extends FeedFile implements Playable {
|
|||||||
|
|
||||||
private void postPlaybackTasks(Context context, boolean completed) {
|
private void postPlaybackTasks(Context context, boolean completed) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
// gpodder play action
|
if (startPosition >= 0 && (completed || startPosition < position) && GpodnetPreferences.loggedIn()) {
|
||||||
if (startPosition >= 0 && (completed || startPosition < position) &&
|
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.PLAY)
|
||||||
GpodnetPreferences.loggedIn()) {
|
|
||||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.PLAY)
|
|
||||||
.currentDeviceId()
|
|
||||||
.currentTimestamp()
|
.currentTimestamp()
|
||||||
.started(startPosition / 1000)
|
.started(startPosition / 1000)
|
||||||
.position((completed ? duration : position) / 1000)
|
.position((completed ? duration : position) / 1000)
|
||||||
.total(duration / 1000)
|
.total(duration / 1000)
|
||||||
.build();
|
.build();
|
||||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
SyncService.enqueueEpisodeAction(context, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GpodnetEpisodeActionGetResponse {
|
|
||||||
|
|
||||||
private final List<GpodnetEpisodeAction> episodeActions;
|
|
||||||
private final long timestamp;
|
|
||||||
|
|
||||||
public GpodnetEpisodeActionGetResponse(@NonNull List<GpodnetEpisodeAction> episodeActions,
|
|
||||||
long timestamp) {
|
|
||||||
this.episodeActions = episodeActions;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GpodnetEpisodeAction> getEpisodeActions() {
|
|
||||||
return this.episodeActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return this.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "GpodnetEpisodeActionGetResponse{" +
|
|
||||||
"episodeActions=" + episodeActions +
|
|
||||||
", timestamp=" + timestamp +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,22 +2,11 @@ package de.danoeh.antennapod.core.preferences;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
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.BuildConfig;
|
||||||
import de.danoeh.antennapod.core.ClientConfig;
|
import de.danoeh.antennapod.core.ClientConfig;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
import de.danoeh.antennapod.core.sync.SyncService;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
|
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||||
import de.danoeh.antennapod.core.service.GpodnetSyncService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages preferences for accessing gpodder.net service
|
* Manages preferences for accessing gpodder.net service
|
||||||
@ -34,66 +23,23 @@ public class GpodnetPreferences {
|
|||||||
private static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID";
|
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_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 username;
|
||||||
private static String password;
|
private static String password;
|
||||||
private static String deviceID;
|
private static String deviceID;
|
||||||
private static String hostname;
|
private static String hostname;
|
||||||
|
|
||||||
private static final ReentrantLock feedListLock = new ReentrantLock();
|
|
||||||
private static Set<String> addedFeeds;
|
|
||||||
private static Set<String> removedFeeds;
|
|
||||||
|
|
||||||
private static List<GpodnetEpisodeAction> queuedEpisodeActions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last value returned by getSubscriptionChanges call. Will be used for all subsequent calls of getSubscriptionChanges.
|
|
||||||
*/
|
|
||||||
private static long lastSubscriptionSyncTimestamp;
|
|
||||||
|
|
||||||
private static long lastEpisodeActionsSyncTimeStamp;
|
|
||||||
|
|
||||||
private static long lastSyncAttemptTimestamp;
|
|
||||||
|
|
||||||
private static boolean lastSyncAttemptResult;
|
|
||||||
|
|
||||||
private static boolean preferencesLoaded = false;
|
private static boolean preferencesLoaded = false;
|
||||||
|
|
||||||
private static SharedPreferences getPreferences() {
|
private static SharedPreferences getPreferences() {
|
||||||
return ClientConfig.applicationCallbacks.getApplicationInstance().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
return ClientConfig.applicationCallbacks.getApplicationInstance().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerOnSharedPreferenceChangeListener(
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener listener) {
|
|
||||||
getPreferences().registerOnSharedPreferenceChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unregisterOnSharedPreferenceChangeListener(
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener listener) {
|
|
||||||
getPreferences().unregisterOnSharedPreferenceChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized void ensurePreferencesLoaded() {
|
private static synchronized void ensurePreferencesLoaded() {
|
||||||
if (!preferencesLoaded) {
|
if (!preferencesLoaded) {
|
||||||
SharedPreferences prefs = getPreferences();
|
SharedPreferences prefs = getPreferences();
|
||||||
username = prefs.getString(PREF_GPODNET_USERNAME, null);
|
username = prefs.getString(PREF_GPODNET_USERNAME, null);
|
||||||
password = prefs.getString(PREF_GPODNET_PASSWORD, null);
|
password = prefs.getString(PREF_GPODNET_PASSWORD, null);
|
||||||
deviceID = prefs.getString(PREF_GPODNET_DEVICEID, 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));
|
hostname = checkGpodnetHostname(prefs.getString(PREF_GPODNET_HOSTNAME, GpodnetService.DEFAULT_BASE_HOST));
|
||||||
|
|
||||||
preferencesLoaded = true;
|
preferencesLoaded = true;
|
||||||
@ -106,24 +52,6 @@ public class GpodnetPreferences {
|
|||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writePreference(String key, long value) {
|
|
||||||
SharedPreferences.Editor editor = getPreferences().edit();
|
|
||||||
editor.putLong(key, value);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writePreference(String key, Collection<String> value) {
|
|
||||||
SharedPreferences.Editor editor = getPreferences().edit();
|
|
||||||
editor.putString(key, writeListToString(value));
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writePreference(String key, boolean value) {
|
|
||||||
SharedPreferences.Editor editor = getPreferences().edit();
|
|
||||||
editor.putBoolean(key, value);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUsername() {
|
public static String getUsername() {
|
||||||
ensurePreferencesLoaded();
|
ensurePreferencesLoaded();
|
||||||
return username;
|
return username;
|
||||||
@ -154,43 +82,6 @@ public class GpodnetPreferences {
|
|||||||
writePreference(PREF_GPODNET_DEVICEID, deviceID);
|
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() {
|
public static String getHostname() {
|
||||||
ensurePreferencesLoaded();
|
ensurePreferencesLoaded();
|
||||||
return hostname;
|
return hostname;
|
||||||
@ -205,92 +96,6 @@ public class GpodnetPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addAddedFeed(String feed) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
if (addedFeeds.add(feed)) {
|
|
||||||
writePreference(PREF_SYNC_ADDED, addedFeeds);
|
|
||||||
}
|
|
||||||
if (removedFeeds.remove(feed)) {
|
|
||||||
writePreference(PREF_SYNC_REMOVED, removedFeeds);
|
|
||||||
}
|
|
||||||
feedListLock.unlock();
|
|
||||||
GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addRemovedFeed(String feed) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
if (removedFeeds.add(feed)) {
|
|
||||||
writePreference(PREF_SYNC_REMOVED, removedFeeds);
|
|
||||||
}
|
|
||||||
if (addedFeeds.remove(feed)) {
|
|
||||||
writePreference(PREF_SYNC_ADDED, addedFeeds);
|
|
||||||
}
|
|
||||||
feedListLock.unlock();
|
|
||||||
GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<String> getAddedFeedsCopy() {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
Set<String> copy = new HashSet<>();
|
|
||||||
feedListLock.lock();
|
|
||||||
copy.addAll(addedFeeds);
|
|
||||||
feedListLock.unlock();
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeAddedFeeds(Collection<String> removed) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
addedFeeds.removeAll(removed);
|
|
||||||
writePreference(PREF_SYNC_ADDED, addedFeeds);
|
|
||||||
feedListLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<String> getRemovedFeedsCopy() {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
Set<String> copy = new HashSet<>();
|
|
||||||
feedListLock.lock();
|
|
||||||
copy.addAll(removedFeeds);
|
|
||||||
feedListLock.unlock();
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeRemovedFeeds(Collection<String> removed) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
removedFeeds.removeAll(removed);
|
|
||||||
writePreference(PREF_SYNC_REMOVED, removedFeeds);
|
|
||||||
feedListLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void enqueueEpisodeAction(GpodnetEpisodeAction action) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
queuedEpisodeActions.add(action);
|
|
||||||
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
|
|
||||||
feedListLock.unlock();
|
|
||||||
GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
List<GpodnetEpisodeAction> copy = new ArrayList<>();
|
|
||||||
feedListLock.lock();
|
|
||||||
copy.addAll(queuedEpisodeActions);
|
|
||||||
feedListLock.unlock();
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeQueuedEpisodeActions(Collection<GpodnetEpisodeAction> queued) {
|
|
||||||
ensurePreferencesLoaded();
|
|
||||||
feedListLock.lock();
|
|
||||||
queuedEpisodeActions.removeAll(queued);
|
|
||||||
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
|
|
||||||
feedListLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if device ID, username and password have a non-null value
|
* Returns true if device ID, username and password have a non-null value
|
||||||
*/
|
*/
|
||||||
@ -304,57 +109,10 @@ public class GpodnetPreferences {
|
|||||||
setUsername(null);
|
setUsername(null);
|
||||||
setPassword(null);
|
setPassword(null);
|
||||||
setDeviceID(null);
|
setDeviceID(null);
|
||||||
feedListLock.lock();
|
SyncService.clearQueue(ClientConfig.applicationCallbacks.getApplicationInstance());
|
||||||
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);
|
|
||||||
UserPreferences.setGpodnetNotificationsEnabled();
|
UserPreferences.setGpodnetNotificationsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> readListFromString(String s) {
|
|
||||||
Set<String> result = new HashSet<>();
|
|
||||||
Collections.addAll(result, s.split(" "));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String writeListToString(Collection<String> c) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
for (String item : c) {
|
|
||||||
result.append(item);
|
|
||||||
result.append(" ");
|
|
||||||
}
|
|
||||||
return result.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<GpodnetEpisodeAction> readEpisodeActionsFromString(String s) {
|
|
||||||
String[] lines = s.split("\n");
|
|
||||||
List<GpodnetEpisodeAction> result = new ArrayList<>(lines.length);
|
|
||||||
for(String line : lines) {
|
|
||||||
if(TextUtils.isEmpty(line)) {
|
|
||||||
GpodnetEpisodeAction action = GpodnetEpisodeAction.readFromString(line);
|
|
||||||
if(action != null) {
|
|
||||||
result.add(GpodnetEpisodeAction.readFromString(line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String writeEpisodeActionsToString(Collection<GpodnetEpisodeAction> c) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
for(GpodnetEpisodeAction item : c) {
|
|
||||||
result.append(item.writeToString());
|
|
||||||
result.append("\n");
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String checkGpodnetHostname(String value) {
|
private static String checkGpodnetHostname(String value) {
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
if (value.startsWith("http://")) {
|
if (value.startsWith("http://")) {
|
||||||
|
@ -1,363 +0,0 @@
|
|||||||
package de.danoeh.antennapod.core.service;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.SafeJobIntentService;
|
|
||||||
import androidx.collection.ArrayMap;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.ClientConfig;
|
|
||||||
import de.danoeh.antennapod.core.R;
|
|
||||||
import de.danoeh.antennapod.core.feed.Feed;
|
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionGetResponse;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionPostResponse;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange;
|
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse;
|
|
||||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
|
||||||
import de.danoeh.antennapod.core.storage.DBReader;
|
|
||||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
|
||||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
|
||||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
|
||||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
|
||||||
import de.danoeh.antennapod.core.util.URLChecker;
|
|
||||||
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument.
|
|
||||||
* This class also provides static methods for starting the GpodnetSyncService.
|
|
||||||
*/
|
|
||||||
public class GpodnetSyncService extends SafeJobIntentService {
|
|
||||||
|
|
||||||
private static final String TAG = "GpodnetSyncService";
|
|
||||||
|
|
||||||
private static final long WAIT_INTERVAL = 5000L;
|
|
||||||
|
|
||||||
private static final String ARG_ACTION = "action";
|
|
||||||
|
|
||||||
private static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
|
|
||||||
private static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
|
|
||||||
private static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
|
|
||||||
|
|
||||||
private GpodnetService service;
|
|
||||||
|
|
||||||
private static final AtomicInteger syncActionCount = new AtomicInteger(0);
|
|
||||||
private static boolean syncSubscriptions = false;
|
|
||||||
private static boolean syncActions = false;
|
|
||||||
|
|
||||||
private static final int JOB_ID = -17000;
|
|
||||||
|
|
||||||
private static void enqueueWork(Context context, Intent intent) {
|
|
||||||
enqueueWork(context, GpodnetSyncService.class, JOB_ID, intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHandleWork(@NonNull Intent intent) {
|
|
||||||
final String action = intent.getStringExtra(ARG_ACTION);
|
|
||||||
if (action != null) {
|
|
||||||
switch(action) {
|
|
||||||
case ACTION_SYNC:
|
|
||||||
syncSubscriptions = true;
|
|
||||||
syncActions = true;
|
|
||||||
break;
|
|
||||||
case ACTION_SYNC_SUBSCRIPTIONS:
|
|
||||||
syncSubscriptions = true;
|
|
||||||
break;
|
|
||||||
case ACTION_SYNC_ACTIONS:
|
|
||||||
syncActions = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.e(TAG, "Received invalid intent: action argument is invalid");
|
|
||||||
}
|
|
||||||
if(syncSubscriptions || syncActions) {
|
|
||||||
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
|
|
||||||
int syncActionId = syncActionCount.incrementAndGet();
|
|
||||||
try {
|
|
||||||
Thread.sleep(WAIT_INTERVAL);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
if (syncActionId == syncActionCount.get()) {
|
|
||||||
// onHandleWork was not called again in the meantime
|
|
||||||
sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Received invalid intent: action argument is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
|
|
||||||
if (service == null) {
|
|
||||||
service = new GpodnetService();
|
|
||||||
service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
|
|
||||||
}
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private synchronized void sync() {
|
|
||||||
if (!GpodnetPreferences.loggedIn() || !NetworkUtils.networkAvailable()) {
|
|
||||||
stopForeground(true);
|
|
||||||
stopSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean initialSync = GpodnetPreferences.getLastSubscriptionSyncTimestamp() == 0 &&
|
|
||||||
GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0;
|
|
||||||
if(syncSubscriptions) {
|
|
||||||
syncSubscriptionChanges();
|
|
||||||
syncSubscriptions = false;
|
|
||||||
}
|
|
||||||
if(syncActions) {
|
|
||||||
// we only sync episode actions after the subscriptions have been added to the database
|
|
||||||
if(!initialSync) {
|
|
||||||
syncEpisodeActions();
|
|
||||||
}
|
|
||||||
syncActions = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void syncSubscriptionChanges() {
|
|
||||||
final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
|
|
||||||
try {
|
|
||||||
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
|
|
||||||
Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
|
|
||||||
Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
|
|
||||||
GpodnetService service = tryLogin();
|
|
||||||
|
|
||||||
// first sync: download all subscriptions...
|
|
||||||
GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
|
|
||||||
GpodnetPreferences.getDeviceID(), timestamp);
|
|
||||||
long newTimeStamp = subscriptionChanges.getTimestamp();
|
|
||||||
|
|
||||||
Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
|
|
||||||
processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
|
|
||||||
|
|
||||||
if(timestamp == 0) {
|
|
||||||
// this is this apps first sync with gpodder:
|
|
||||||
// only submit changes gpodder has not just sent us
|
|
||||||
localAdded = localSubscriptions;
|
|
||||||
localAdded.removeAll(subscriptionChanges.getAdded());
|
|
||||||
localRemoved.removeAll(subscriptionChanges.getRemoved());
|
|
||||||
}
|
|
||||||
if(localAdded.size() > 0 || localRemoved.size() > 0) {
|
|
||||||
Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
|
|
||||||
localAdded, localRemoved));
|
|
||||||
GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
|
|
||||||
GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
|
|
||||||
newTimeStamp = uploadResponse.timestamp;
|
|
||||||
Log.d(TAG, "Upload changes response: " + uploadResponse);
|
|
||||||
GpodnetPreferences.removeAddedFeeds(localAdded);
|
|
||||||
GpodnetPreferences.removeRemovedFeeds(localRemoved);
|
|
||||||
}
|
|
||||||
GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
|
|
||||||
GpodnetPreferences.setLastSyncAttempt(true, System.currentTimeMillis());
|
|
||||||
clearErrorNotifications();
|
|
||||||
} catch (GpodnetServiceException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
updateErrorNotification(e);
|
|
||||||
} catch (DownloadRequestException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void processSubscriptionChanges(List<String> localSubscriptions,
|
|
||||||
Collection<String> localAdded,
|
|
||||||
Collection<String> localRemoved,
|
|
||||||
GpodnetSubscriptionChange changes) throws DownloadRequestException {
|
|
||||||
// local changes are always superior to remote changes!
|
|
||||||
// add subscription if (1) not already subscribed and (2) not just unsubscribed
|
|
||||||
for (String downloadUrl : changes.getAdded()) {
|
|
||||||
if (!URLChecker.containsUrl(localSubscriptions, downloadUrl) && !localRemoved.contains(downloadUrl)) {
|
|
||||||
Feed feed = new Feed(downloadUrl, null);
|
|
||||||
DownloadRequester.getInstance().downloadFeed(this, feed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove subscription if not just subscribed (again)
|
|
||||||
for (String downloadUrl : changes.getRemoved()) {
|
|
||||||
if (!localAdded.contains(downloadUrl)) {
|
|
||||||
DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void syncEpisodeActions() {
|
|
||||||
final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp();
|
|
||||||
Log.d(TAG, "last episode actions sync timestamp: " + timestamp);
|
|
||||||
try {
|
|
||||||
GpodnetService service = tryLogin();
|
|
||||||
|
|
||||||
// download episode actions
|
|
||||||
GpodnetEpisodeActionGetResponse getResponse = service.getEpisodeChanges(timestamp);
|
|
||||||
long lastUpdate = getResponse.getTimestamp();
|
|
||||||
Log.d(TAG, "Downloaded episode actions: " + getResponse);
|
|
||||||
List<GpodnetEpisodeAction> remoteActions = getResponse.getEpisodeActions();
|
|
||||||
|
|
||||||
List<GpodnetEpisodeAction> localActions = GpodnetPreferences.getQueuedEpisodeActions();
|
|
||||||
processEpisodeActions(localActions, remoteActions);
|
|
||||||
|
|
||||||
// upload local actions
|
|
||||||
if(localActions.size() > 0) {
|
|
||||||
Log.d(TAG, "Uploading episode actions: " + localActions);
|
|
||||||
GpodnetEpisodeActionPostResponse postResponse = service.uploadEpisodeActions(localActions);
|
|
||||||
lastUpdate = postResponse.timestamp;
|
|
||||||
Log.d(TAG, "Upload episode response: " + postResponse);
|
|
||||||
GpodnetPreferences.removeQueuedEpisodeActions(localActions);
|
|
||||||
}
|
|
||||||
GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(lastUpdate);
|
|
||||||
GpodnetPreferences.setLastSyncAttempt(true, System.currentTimeMillis());
|
|
||||||
clearErrorNotifications();
|
|
||||||
} catch (GpodnetServiceException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
updateErrorNotification(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
|
|
||||||
List<GpodnetEpisodeAction> remoteActions) {
|
|
||||||
if(remoteActions.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new ArrayMap<>();
|
|
||||||
for(GpodnetEpisodeAction action : localActions) {
|
|
||||||
Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
|
|
||||||
GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
|
|
||||||
if (mostRecent == null || mostRecent.getTimestamp() == null) {
|
|
||||||
localMostRecentPlayAction.put(key, action);
|
|
||||||
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
|
|
||||||
localMostRecentPlayAction.put(key, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure more recent local actions are not overwritten by older remote actions
|
|
||||||
Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new ArrayMap<>();
|
|
||||||
for (GpodnetEpisodeAction action : remoteActions) {
|
|
||||||
switch (action.getAction()) {
|
|
||||||
case NEW:
|
|
||||||
FeedItem newItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
|
|
||||||
if(newItem != null) {
|
|
||||||
DBWriter.markItemPlayed(newItem, FeedItem.UNPLAYED, true);
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Unknown feed item: " + action);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DOWNLOAD:
|
|
||||||
break;
|
|
||||||
case PLAY:
|
|
||||||
Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
|
|
||||||
GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
|
|
||||||
if(localMostRecent == null ||
|
|
||||||
localMostRecent.getTimestamp() == null ||
|
|
||||||
localMostRecent.getTimestamp().before(action.getTimestamp())) {
|
|
||||||
GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
|
|
||||||
if (mostRecent == null || mostRecent.getTimestamp() == null) {
|
|
||||||
mostRecentPlayAction.put(key, action);
|
|
||||||
} else if (action.getTimestamp() != null && mostRecent.getTimestamp().before(action.getTimestamp())) {
|
|
||||||
mostRecentPlayAction.put(key, action);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "No date information in action, skipping it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DELETE:
|
|
||||||
// NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
|
|
||||||
FeedItem playItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
|
|
||||||
if (playItem != null) {
|
|
||||||
FeedMedia media = playItem.getMedia();
|
|
||||||
media.setPosition(action.getPosition() * 1000);
|
|
||||||
DBWriter.setFeedMedia(media);
|
|
||||||
if(playItem.getMedia().hasAlmostEnded()) {
|
|
||||||
DBWriter.markItemPlayed(playItem, FeedItem.PLAYED, true);
|
|
||||||
DBWriter.addItemToPlaybackHistory(playItem.getMedia());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearErrorNotifications() {
|
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
|
||||||
nm.cancel(R.id.notification_gpodnet_sync_error);
|
|
||||||
nm.cancel(R.id.notification_gpodnet_sync_autherror);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateErrorNotification(GpodnetServiceException exception) {
|
|
||||||
Log.d(TAG, "Posting error notification");
|
|
||||||
GpodnetPreferences.setLastSyncAttempt(false, System.currentTimeMillis());
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final int id;
|
|
||||||
if (exception instanceof GpodnetServiceAuthenticationException) {
|
|
||||||
title = getString(R.string.gpodnetsync_auth_error_title);
|
|
||||||
description = getString(R.string.gpodnetsync_auth_error_descr);
|
|
||||||
id = R.id.notification_gpodnet_sync_autherror;
|
|
||||||
} else {
|
|
||||||
if (UserPreferences.gpodnetNotificationsEnabled()) {
|
|
||||||
title = getString(R.string.gpodnetsync_error_title);
|
|
||||||
description = getString(R.string.gpodnetsync_error_descr) + exception.getMessage();
|
|
||||||
id = R.id.notification_gpodnet_sync_error;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingIntent activityIntent = ClientConfig.gpodnetCallbacks.getGpodnetSyncServiceErrorNotificationPendingIntent(this);
|
|
||||||
Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setContentText(description)
|
|
||||||
.setContentIntent(activityIntent)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification_sync_error)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
.build();
|
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
nm.notify(id, notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendSyncIntent(Context context) {
|
|
||||||
if (GpodnetPreferences.loggedIn()) {
|
|
||||||
Intent intent = new Intent(context, GpodnetSyncService.class);
|
|
||||||
intent.putExtra(ARG_ACTION, ACTION_SYNC);
|
|
||||||
enqueueWork(context, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendSyncSubscriptionsIntent(Context context) {
|
|
||||||
if (GpodnetPreferences.loggedIn()) {
|
|
||||||
Intent intent = new Intent(context, GpodnetSyncService.class);
|
|
||||||
intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
|
|
||||||
enqueueWork(context, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendSyncActionsIntent(Context context) {
|
|
||||||
if (GpodnetPreferences.loggedIn()) {
|
|
||||||
Intent intent = new Intent(context, GpodnetSyncService.class);
|
|
||||||
intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
|
|
||||||
enqueueWork(context, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.core.sync.SyncService;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.greenrobot.eventbus.EventBus;
|
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.Feed;
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
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.FailedDownloadHandler;
|
||||||
import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
|
import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
|
||||||
import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
|
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,
|
// if this was the initial gpodder sync, i.e. we just synced the feeds successfully,
|
||||||
// it is now time to sync the episode actions
|
// it is now time to sync the episode actions
|
||||||
if (GpodnetPreferences.loggedIn() &&
|
SyncService.sync(this);
|
||||||
GpodnetPreferences.getLastSubscriptionSyncTimestamp() > 0 &&
|
|
||||||
GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0) {
|
|
||||||
GpodnetSyncService.sendSyncActionsIntent(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start auto download in case anything new has shown up
|
// start auto download in case anything new has shown up
|
||||||
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
|
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
|
||||||
@ -554,7 +549,7 @@ public class DownloadService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if there's something else to download, otherwise stop
|
* Check if there's something else to download, otherwise stop.
|
||||||
*/
|
*/
|
||||||
private void queryDownloads() {
|
private void queryDownloads() {
|
||||||
Log.d(TAG, numberOfDownloads.get() + " downloads left");
|
Log.d(TAG, numberOfDownloads.get() + " downloads left");
|
||||||
@ -562,7 +557,11 @@ public class DownloadService extends Service {
|
|||||||
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
|
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
|
||||||
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
|
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
|
||||||
stopSelf();
|
stopSelf();
|
||||||
notificationUpdater.run();
|
if (notificationUpdater != null) {
|
||||||
|
notificationUpdater.run();
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Skipping notification update");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setupNotificationUpdater();
|
setupNotificationUpdater();
|
||||||
Notification notification = notificationManager.updateNotifications(
|
Notification notification = notificationManager.updateNotifications(
|
||||||
|
@ -12,14 +12,14 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.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.DownloadRequest;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
import de.danoeh.antennapod.core.storage.DBReader;
|
import de.danoeh.antennapod.core.storage.DBReader;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||||
import de.danoeh.antennapod.core.util.DownloadError;
|
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;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,12 +99,11 @@ public class MediaDownloadedHandler implements Runnable {
|
|||||||
DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage(), request.isInitiatedByUser());
|
DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage(), request.isInitiatedByUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GpodnetPreferences.loggedIn() && item != null) {
|
if (item != null) {
|
||||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DOWNLOAD)
|
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
|
||||||
.currentDeviceId()
|
|
||||||
.currentTimestamp()
|
.currentTimestamp()
|
||||||
.build();
|
.build();
|
||||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
SyncService.enqueueEpisodeAction(context, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
|
|||||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
|
import de.danoeh.antennapod.core.sync.model.EpisodeAction;
|
||||||
import de.danoeh.antennapod.core.util.LongIntMap;
|
import de.danoeh.antennapod.core.util.LongIntMap;
|
||||||
import de.danoeh.antennapod.core.util.LongList;
|
import de.danoeh.antennapod.core.util.LongList;
|
||||||
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
|
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
|
||||||
@ -347,6 +348,31 @@ public final class DBReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a list of FeedItems whose episode has been played.
|
||||||
|
*
|
||||||
|
* @return A list of FeedItems whose episdoe has been played.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static List<FeedItem> getPlayedItems() {
|
||||||
|
Log.d(TAG, "getPlayedItems() called");
|
||||||
|
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = adapter.getPlayedItemsCursor();
|
||||||
|
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
|
||||||
|
loadAdditionalFeedItemListData(items);
|
||||||
|
return items;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
adapter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a list of FeedItems that are considered new.
|
* Loads a list of FeedItems that are considered new.
|
||||||
* Excludes items from feeds that do not have keep updated enabled.
|
* Excludes items from feeds that do not have keep updated enabled.
|
||||||
@ -629,8 +655,16 @@ public final class DBReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a specific FeedItem from the database.
|
||||||
|
*
|
||||||
|
* @param podcastUrl the corresponding feed's url
|
||||||
|
* @param episodeUrl the feed item's url
|
||||||
|
* @return The FeedItem or null if the FeedItem could not be found.
|
||||||
|
* Does NOT load additional attributes like feed or queue state.
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
|
private static FeedItem getFeedItemByUrl(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
|
||||||
Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
|
Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
@ -639,15 +673,10 @@ public final class DBReader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<FeedItem> list = extractItemlistFromCursor(adapter, cursor);
|
List<FeedItem> list = extractItemlistFromCursor(adapter, cursor);
|
||||||
FeedItem item = null;
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
item = list.get(0);
|
return list.get(0);
|
||||||
loadAdditionalFeedItemListData(list);
|
|
||||||
if (item.hasChapters()) {
|
|
||||||
loadChaptersOfFeedItem(adapter, item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return item;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
@ -702,16 +731,16 @@ public final class DBReader {
|
|||||||
*
|
*
|
||||||
* @param podcastUrl the corresponding feed's url
|
* @param podcastUrl the corresponding feed's url
|
||||||
* @param episodeUrl the feed item's url
|
* @param episodeUrl the feed item's url
|
||||||
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
|
* @return The FeedItem or null if the FeedItem could not be found.
|
||||||
* as well as chapter marks of the FeedItem will also be loaded from the database.
|
* Does NOT load additional attributes like feed or queue state.
|
||||||
*/
|
*/
|
||||||
public static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl) {
|
public static FeedItem getFeedItemByUrl(final String podcastUrl, final String episodeUrl) {
|
||||||
Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
|
Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
|
||||||
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
adapter.open();
|
adapter.open();
|
||||||
try {
|
try {
|
||||||
return getFeedItem(podcastUrl, episodeUrl, adapter);
|
return getFeedItemByUrl(podcastUrl, episodeUrl, adapter);
|
||||||
} finally {
|
} finally {
|
||||||
adapter.close();
|
adapter.close();
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ import de.danoeh.antennapod.core.feed.FeedItem;
|
|||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
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.service.download.DownloadStatus;
|
||||||
|
import de.danoeh.antennapod.core.sync.SyncService;
|
||||||
import de.danoeh.antennapod.core.util.DownloadError;
|
import de.danoeh.antennapod.core.util.DownloadError;
|
||||||
import de.danoeh.antennapod.core.util.LongList;
|
import de.danoeh.antennapod.core.util.LongList;
|
||||||
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
|
||||||
@ -123,9 +123,7 @@ public final class DBTasks {
|
|||||||
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
|
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
|
||||||
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
|
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
|
||||||
|
|
||||||
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
|
SyncService.sync(context);
|
||||||
GpodnetSyncService.sendSyncIntent(context);
|
|
||||||
}
|
|
||||||
// Note: automatic download of episodes will be done but not here.
|
// Note: automatic download of episodes will be done but not here.
|
||||||
// Instead it is done after all feeds have been refreshed (asynchronously),
|
// Instead it is done after all feeds have been refreshed (asynchronously),
|
||||||
// in DownloadService.onDestroy()
|
// in DownloadService.onDestroy()
|
||||||
|
@ -7,6 +7,8 @@ import android.util.Log;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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 org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import java.io.File;
|
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.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
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.GpodnetPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
@ -118,11 +119,10 @@ public class DBWriter {
|
|||||||
// Gpodder: queue delete action for synchronization
|
// Gpodder: queue delete action for synchronization
|
||||||
if (GpodnetPreferences.loggedIn()) {
|
if (GpodnetPreferences.loggedIn()) {
|
||||||
FeedItem item = media.getItem();
|
FeedItem item = media.getItem();
|
||||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
|
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
|
||||||
.currentDeviceId()
|
|
||||||
.currentTimestamp()
|
.currentTimestamp()
|
||||||
.build();
|
.build();
|
||||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
SyncService.enqueueEpisodeAction(context, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
|
EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
|
||||||
@ -169,9 +169,7 @@ public class DBWriter {
|
|||||||
adapter.removeFeed(feed);
|
adapter.removeFeed(feed);
|
||||||
adapter.close();
|
adapter.close();
|
||||||
|
|
||||||
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
|
SyncService.enqueueFeedRemoved(context, feed.getDownload_url());
|
||||||
GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
|
|
||||||
}
|
|
||||||
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
|
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
|
||||||
|
|
||||||
// we assume we also removed download log entries for the feed or its media files.
|
// 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.setCompleteFeed(feeds);
|
||||||
adapter.close();
|
adapter.close();
|
||||||
|
|
||||||
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
|
for (Feed feed : feeds) {
|
||||||
for (Feed feed : feeds) {
|
SyncService.enqueueFeedAdded(context, feed.getDownload_url());
|
||||||
GpodnetPreferences.addAddedFeed(feed.getDownload_url());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupManager backupManager = new BackupManager(context);
|
BackupManager backupManager = new BackupManager(context);
|
||||||
@ -747,6 +743,15 @@ public class DBWriter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Future<?> setItemList(final List<FeedItem> items) {
|
||||||
|
return dbExec.submit(() -> {
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
adapter.setFeedItemlist(items);
|
||||||
|
adapter.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
|
* Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
|
||||||
* contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
|
* contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
|
||||||
|
@ -1023,6 +1023,15 @@ public class PodDBAdapter {
|
|||||||
return db.rawQuery(query, null);
|
return db.rawQuery(query, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cursor getPlayedItemsCursor() {
|
||||||
|
final String query = "SELECT " + SEL_FI_SMALL_STR
|
||||||
|
+ " FROM " + TABLE_NAME_FEED_ITEMS
|
||||||
|
+ " INNER JOIN " + TABLE_NAME_FEED_MEDIA
|
||||||
|
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
|
||||||
|
+ " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.PLAYED;
|
||||||
|
return db.rawQuery(query, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a cursor which contains feed media objects with a playback
|
* Returns a cursor which contains feed media objects with a playback
|
||||||
* completion date in ascending order.
|
* completion date in ascending order.
|
||||||
|
@ -0,0 +1,461 @@
|
|||||||
|
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.util.Pair;
|
||||||
|
import androidx.work.BackoffPolicy;
|
||||||
|
import androidx.work.Constraints;
|
||||||
|
import androidx.work.ExistingWorkPolicy;
|
||||||
|
import androidx.work.NetworkType;
|
||||||
|
import androidx.work.OneTimeWorkRequest;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
|
import androidx.work.Worker;
|
||||||
|
import androidx.work.WorkerParameters;
|
||||||
|
import de.danoeh.antennapod.core.R;
|
||||||
|
import de.danoeh.antennapod.core.event.SyncServiceEvent;
|
||||||
|
import de.danoeh.antennapod.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.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.URLChecker;
|
||||||
|
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class SyncService extends Worker {
|
||||||
|
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 PREF_LAST_SYNC_ATTEMPT_SUCCESS = "last_sync_attempt_success";
|
||||||
|
private static final String TAG = "SyncService";
|
||||||
|
private static final String WORK_ID_SYNC = "SyncServiceWorkId";
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
|
private ISyncService syncServiceImpl;
|
||||||
|
|
||||||
|
public SyncService(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||||
|
super(context, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Result doWork() {
|
||||||
|
if (!GpodnetPreferences.loggedIn()) {
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
syncServiceImpl = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST);
|
||||||
|
SharedPreferences.Editor prefs = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit();
|
||||||
|
prefs.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, System.currentTimeMillis()).apply();
|
||||||
|
try {
|
||||||
|
syncServiceImpl.login();
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_subscriptions));
|
||||||
|
syncSubscriptions();
|
||||||
|
syncEpisodeActions();
|
||||||
|
syncServiceImpl.logout();
|
||||||
|
clearErrorNotifications();
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_success));
|
||||||
|
prefs.putBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, true).apply();
|
||||||
|
return Result.success();
|
||||||
|
} catch (SyncServiceException e) {
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_error));
|
||||||
|
prefs.putBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false).apply();
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
|
updateErrorNotification(e);
|
||||||
|
return Result.retry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
OneTimeWorkRequest workRequest = getWorkRequest().build();
|
||||||
|
WorkManager.getInstance().enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncImmediately(Context context) {
|
||||||
|
OneTimeWorkRequest workRequest = getWorkRequest()
|
||||||
|
.setInitialDelay(0L, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
WorkManager.getInstance().enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
OneTimeWorkRequest workRequest = getWorkRequest()
|
||||||
|
.setInitialDelay(0L, TimeUnit.SECONDS)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
WorkManager.getInstance().enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OneTimeWorkRequest.Builder getWorkRequest() {
|
||||||
|
Constraints.Builder constraints = new Constraints.Builder();
|
||||||
|
if (UserPreferences.isAllowMobileFeedRefresh()) {
|
||||||
|
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
|
||||||
|
} else {
|
||||||
|
constraints.setRequiredNetworkType(NetworkType.UNMETERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OneTimeWorkRequest.Builder(SyncService.class)
|
||||||
|
.setConstraints(constraints.build())
|
||||||
|
.setInitialDelay(5L, TimeUnit.SECONDS); // Give it some time, so other actions can be queued
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLastSyncSuccessful(Context context) {
|
||||||
|
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getLastSyncAttempt(Context context) {
|
||||||
|
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EpisodeAction> getQueuedEpisodeActions() {
|
||||||
|
ArrayList<EpisodeAction> actions = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
String json = prefs.getString(PREF_QUEUED_EPISODE_ACTIONS, "[]");
|
||||||
|
JSONArray queue = new JSONArray(json);
|
||||||
|
for (int i = 0; i < queue.length(); i++) {
|
||||||
|
actions.add(EpisodeAction.readFromJsonObject(queue.getJSONObject(i)));
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getQueuedRemovedFeeds() {
|
||||||
|
ArrayList<String> actions = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
String json = prefs.getString(PREF_QUEUED_FEEDS_REMOVED, "[]");
|
||||||
|
JSONArray queue = new JSONArray(json);
|
||||||
|
for (int i = 0; i < queue.length(); i++) {
|
||||||
|
actions.add(queue.getString(i));
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getQueuedAddedFeeds() {
|
||||||
|
ArrayList<String> actions = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
SharedPreferences prefs = getApplicationContext().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 = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0);
|
||||||
|
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
|
||||||
|
SubscriptionChanges subscriptionChanges = syncServiceImpl.getSubscriptionChanges(lastSync);
|
||||||
|
long newTimeStamp = subscriptionChanges.getTimestamp();
|
||||||
|
|
||||||
|
List<String> queuedRemovedFeeds = getQueuedRemovedFeeds();
|
||||||
|
List<String> queuedAddedFeeds = getQueuedAddedFeeds();
|
||||||
|
|
||||||
|
Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
|
||||||
|
for (String downloadUrl : subscriptionChanges.getAdded()) {
|
||||||
|
if (!URLChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) {
|
||||||
|
Feed feed = new Feed(downloadUrl, null);
|
||||||
|
try {
|
||||||
|
DownloadRequester.getInstance().downloadFeed(getApplicationContext(), feed);
|
||||||
|
} catch (DownloadRequestException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove subscription if not just subscribed (again)
|
||||||
|
for (String downloadUrl : subscriptionChanges.getRemoved()) {
|
||||||
|
if (!queuedAddedFeeds.contains(downloadUrl)) {
|
||||||
|
DBTasks.removeFeedWithDownloadUrl(getApplicationContext(), 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);
|
||||||
|
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
|
||||||
|
.putString(PREF_QUEUED_FEEDS_ADDED, "[]").apply();
|
||||||
|
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
|
||||||
|
.putString(PREF_QUEUED_FEEDS_REMOVED, "[]").apply();
|
||||||
|
newTimeStamp = uploadResponse.timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
|
||||||
|
.putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, newTimeStamp).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncEpisodeActions() throws SyncServiceException {
|
||||||
|
final long lastSync = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0);
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_episodes_download));
|
||||||
|
EpisodeActionChanges getResponse = syncServiceImpl.getEpisodeActionChanges(lastSync);
|
||||||
|
long newTimeStamp = getResponse.getTimestamp();
|
||||||
|
List<EpisodeAction> remoteActions = getResponse.getEpisodeActions();
|
||||||
|
processEpisodeActions(remoteActions);
|
||||||
|
|
||||||
|
// upload local actions
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_episodes_upload));
|
||||||
|
List<EpisodeAction> queuedEpisodeActions = getQueuedEpisodeActions();
|
||||||
|
if (lastSync == 0) {
|
||||||
|
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_upload_played));
|
||||||
|
List<FeedItem> readItems = DBReader.getPlayedItems();
|
||||||
|
Log.d(TAG, "First sync. Upload state for all " + readItems.size() + " played episodes");
|
||||||
|
for (FeedItem item : readItems) {
|
||||||
|
FeedMedia media = item.getMedia();
|
||||||
|
if (media == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EpisodeAction played = new EpisodeAction.Builder(item, EpisodeAction.PLAY)
|
||||||
|
.currentTimestamp()
|
||||||
|
.started(media.getDuration() / 1000)
|
||||||
|
.position(media.getDuration() / 1000)
|
||||||
|
.total(media.getDuration() / 1000)
|
||||||
|
.build();
|
||||||
|
queuedEpisodeActions.add(played);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queuedEpisodeActions.size() > 0) {
|
||||||
|
synchronized (lock) {
|
||||||
|
Log.d(TAG, "Uploading " + queuedEpisodeActions.size() + " actions: "
|
||||||
|
+ StringUtils.join(queuedEpisodeActions, ", "));
|
||||||
|
UploadChangesResponse postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions);
|
||||||
|
newTimeStamp = postResponse.timestamp;
|
||||||
|
Log.d(TAG, "Upload episode response: " + postResponse);
|
||||||
|
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
|
||||||
|
.putString(PREF_QUEUED_EPISODE_ACTIONS, "[]").apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
|
||||||
|
.putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, newTimeStamp).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private synchronized void processEpisodeActions(List<EpisodeAction> remoteActions) {
|
||||||
|
Log.d(TAG, "Processing " + remoteActions.size() + " actions");
|
||||||
|
if (remoteActions.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Pair<String, String>, EpisodeAction> localMostRecentPlayAction = new ArrayMap<>();
|
||||||
|
for (EpisodeAction action : getQueuedEpisodeActions()) {
|
||||||
|
Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
|
||||||
|
EpisodeAction mostRecent = localMostRecentPlayAction.get(key);
|
||||||
|
if (mostRecent == null || mostRecent.getTimestamp() == null) {
|
||||||
|
localMostRecentPlayAction.put(key, action);
|
||||||
|
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
|
||||||
|
localMostRecentPlayAction.put(key, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure more recent local actions are not overwritten by older remote actions
|
||||||
|
Map<Pair<String, String>, EpisodeAction> mostRecentPlayAction = new ArrayMap<>();
|
||||||
|
for (EpisodeAction action : remoteActions) {
|
||||||
|
Log.d(TAG, "Processing action: " + action.toString());
|
||||||
|
switch (action.getAction()) {
|
||||||
|
case NEW:
|
||||||
|
FeedItem newItem = DBReader.getFeedItemByUrl(action.getPodcast(), action.getEpisode());
|
||||||
|
if (newItem != null) {
|
||||||
|
DBWriter.markItemPlayed(newItem, FeedItem.UNPLAYED, true);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Unknown feed item: " + action);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DOWNLOAD:
|
||||||
|
break;
|
||||||
|
case PLAY:
|
||||||
|
Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
|
||||||
|
EpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
|
||||||
|
if (localMostRecent == null || localMostRecent.getTimestamp() == null
|
||||||
|
|| localMostRecent.getTimestamp().before(action.getTimestamp())) {
|
||||||
|
EpisodeAction mostRecent = mostRecentPlayAction.get(key);
|
||||||
|
if (mostRecent == null || mostRecent.getTimestamp() == null) {
|
||||||
|
mostRecentPlayAction.put(key, action);
|
||||||
|
} else if (action.getTimestamp() != null
|
||||||
|
&& mostRecent.getTimestamp().before(action.getTimestamp())) {
|
||||||
|
mostRecentPlayAction.put(key, action);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No date information in action, skipping it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
// NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Unknown action: " + action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FeedItem> updatedItems = new ArrayList<>();
|
||||||
|
for (EpisodeAction action : mostRecentPlayAction.values()) {
|
||||||
|
FeedItem playItem = DBReader.getFeedItemByUrl(action.getPodcast(), action.getEpisode());
|
||||||
|
Log.d(TAG, "Most recent play action: " + action.toString());
|
||||||
|
if (playItem != null) {
|
||||||
|
FeedMedia media = playItem.getMedia();
|
||||||
|
media.setPosition(action.getPosition() * 1000);
|
||||||
|
if (playItem.getMedia().hasAlmostEnded()) {
|
||||||
|
Log.d(TAG, "Marking as played");
|
||||||
|
playItem.setPlayed(true);
|
||||||
|
}
|
||||||
|
updatedItems.add(playItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBWriter.setItemList(updatedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearErrorNotifications() {
|
||||||
|
NotificationManager nm = (NotificationManager) getApplicationContext()
|
||||||
|
.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 = getApplicationContext().getString(R.string.gpodnetsync_error_descr)
|
||||||
|
+ exception.getMessage();
|
||||||
|
|
||||||
|
Intent intent = getApplicationContext().getPackageManager().getLaunchIntentForPackage(
|
||||||
|
getApplicationContext().getPackageName());
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
Notification notification = new NotificationCompat.Builder(getApplicationContext(),
|
||||||
|
NotificationUtils.CHANNEL_ID_ERROR)
|
||||||
|
.setContentTitle(getApplicationContext().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) getApplicationContext()
|
||||||
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
nm.notify(R.id.notification_gpodnet_sync_error, notification);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,27 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet;
|
package de.danoeh.antennapod.core.sync.gpoddernet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
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.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -14,63 +34,43 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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.
|
* Communicates with the gpodder.net service.
|
||||||
*/
|
*/
|
||||||
public class GpodnetService {
|
public class GpodnetService implements ISyncService {
|
||||||
|
public static final String TAG = "GpodnetService";
|
||||||
private static final String TAG = "GpodnetService";
|
|
||||||
|
|
||||||
private static final String BASE_SCHEME = "https";
|
|
||||||
|
|
||||||
public static final String DEFAULT_BASE_HOST = "gpodder.net";
|
public static final String DEFAULT_BASE_HOST = "gpodder.net";
|
||||||
private final String BASE_HOST;
|
private static final String BASE_SCHEME = "https";
|
||||||
|
private static final int UPLOAD_BULK_SIZE = 30;
|
||||||
private static final MediaType TEXT = MediaType.parse("plain/text; charset=utf-8");
|
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 static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
private final String baseHost;
|
||||||
private final OkHttpClient httpClient;
|
private final OkHttpClient httpClient;
|
||||||
|
private String username = null;
|
||||||
|
|
||||||
|
public GpodnetService(OkHttpClient httpClient, String baseHost) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.baseHost = baseHost;
|
||||||
|
}
|
||||||
|
|
||||||
public GpodnetService() {
|
private void requireLoggedIn() {
|
||||||
httpClient = AntennapodHttpClient.getHttpClient();
|
if (username == null) {
|
||||||
BASE_HOST = GpodnetPreferences.getHostname();
|
throw new IllegalStateException("Not logged in");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the [count] most used tags.
|
* Returns the [count] most used tags.
|
||||||
*/
|
*/
|
||||||
public List<GpodnetTag> getTopTags(int count)
|
public List<GpodnetTag> getTopTags(int count) throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
|
||||||
URL url;
|
URL url;
|
||||||
try {
|
try {
|
||||||
url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/tags/%d.json", count), null).toURL();
|
||||||
"/api/2/tags/%d.json", count), null).toURL();
|
|
||||||
} catch (MalformedURLException | URISyntaxException e) {
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new GpodnetServiceException(e);
|
||||||
@ -80,13 +80,12 @@ public class GpodnetService {
|
|||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
try {
|
try {
|
||||||
JSONArray jsonTagList = new JSONArray(response);
|
JSONArray jsonTagList = new JSONArray(response);
|
||||||
List<GpodnetTag> tagList = new ArrayList<>(
|
List<GpodnetTag> tagList = new ArrayList<>(jsonTagList.length());
|
||||||
jsonTagList.length());
|
|
||||||
for (int i = 0; i < jsonTagList.length(); i++) {
|
for (int i = 0; i < jsonTagList.length(); i++) {
|
||||||
JSONObject jObj = jsonTagList.getJSONObject(i);
|
JSONObject jsonObject = jsonTagList.getJSONObject(i);
|
||||||
String title = jObj.getString("title");
|
String title = jsonObject.getString("title");
|
||||||
String tag = jObj.getString("tag");
|
String tag = jsonObject.getString("tag");
|
||||||
int usage = jObj.getInt("usage");
|
int usage = jsonObject.getInt("usage");
|
||||||
tagList.add(new GpodnetTag(title, tag, usage));
|
tagList.add(new GpodnetTag(title, tag, usage));
|
||||||
}
|
}
|
||||||
return tagList;
|
return tagList;
|
||||||
@ -101,17 +100,16 @@ public class GpodnetService {
|
|||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if tag is null
|
* @throws IllegalArgumentException if tag is null
|
||||||
*/
|
*/
|
||||||
public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag,
|
public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag, int count)
|
||||||
int count)
|
|
||||||
throws GpodnetServiceException {
|
throws GpodnetServiceException {
|
||||||
try {
|
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();
|
"/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
|
|
||||||
JSONArray jsonArray = new JSONArray(response);
|
JSONArray jsonArray = new JSONArray(response);
|
||||||
return readPodcastListFromJSONArray(jsonArray);
|
return readPodcastListFromJsonArray(jsonArray);
|
||||||
|
|
||||||
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -125,20 +123,18 @@ public class GpodnetService {
|
|||||||
* @param count of elements that should be returned. Must be in range 1..100.
|
* @param count of elements that should be returned. Must be in range 1..100.
|
||||||
* @throws IllegalArgumentException if count is out of range.
|
* @throws IllegalArgumentException if count is out of range.
|
||||||
*/
|
*/
|
||||||
public List<GpodnetPodcast> getPodcastToplist(int count)
|
public List<GpodnetPodcast> getPodcastToplist(int count) throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
if (count < 1 || count > 100) {
|
||||||
if(count < 1 || count > 100) {
|
|
||||||
throw new IllegalArgumentException("Count must be in range 1..100");
|
throw new IllegalArgumentException("Count must be in range 1..100");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
URL url = new URI(BASE_SCHEME, baseHost, String.format("/toplist/%d.json", count), null).toURL();
|
||||||
"/toplist/%d.json", count), null).toURL();
|
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
|
|
||||||
JSONArray jsonArray = new JSONArray(response);
|
JSONArray jsonArray = new JSONArray(response);
|
||||||
return readPodcastListFromJSONArray(jsonArray);
|
return readPodcastListFromJsonArray(jsonArray);
|
||||||
|
|
||||||
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -159,18 +155,17 @@ public class GpodnetService {
|
|||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public List<GpodnetPodcast> getSuggestions(int count) throws GpodnetServiceException {
|
public List<GpodnetPodcast> getSuggestions(int count) throws GpodnetServiceException {
|
||||||
if(count < 1 || count > 100) {
|
if (count < 1 || count > 100) {
|
||||||
throw new IllegalArgumentException("Count must be in range 1..100");
|
throw new IllegalArgumentException("Count must be in range 1..100");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
URL url = new URI(BASE_SCHEME, baseHost, String.format("/suggestions/%d.json", count), null).toURL();
|
||||||
"/suggestions/%d.json", count), null).toURL();
|
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
|
|
||||||
JSONArray jsonArray = new JSONArray(response);
|
JSONArray jsonArray = new JSONArray(response);
|
||||||
return readPodcastListFromJSONArray(jsonArray);
|
return readPodcastListFromJsonArray(jsonArray);
|
||||||
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new GpodnetServiceException(e);
|
||||||
@ -185,19 +180,18 @@ public class GpodnetService {
|
|||||||
* Must be in range 1..256. If the value is out of range, the
|
* 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.
|
* default value defined by the gpodder.net API will be used.
|
||||||
*/
|
*/
|
||||||
public List<GpodnetPodcast> searchPodcasts(String query, int scaledLogoSize)
|
public List<GpodnetPodcast> searchPodcasts(String query, int scaledLogoSize) throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
|
||||||
String parameters = (scaledLogoSize > 0 && scaledLogoSize <= 256) ? String
|
String parameters = (scaledLogoSize > 0 && scaledLogoSize <= 256) ? String
|
||||||
.format("q=%s&scale_logo=%d", query, scaledLogoSize) : String
|
.format("q=%s&scale_logo=%d", query, scaledLogoSize) : String
|
||||||
.format("q=%s", query);
|
.format("q=%s", query);
|
||||||
try {
|
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();
|
parameters, null).toURL();
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
|
|
||||||
JSONArray jsonArray = new JSONArray(response);
|
JSONArray jsonArray = new JSONArray(response);
|
||||||
return readPodcastListFromJSONArray(jsonArray);
|
return readPodcastListFromJsonArray(jsonArray);
|
||||||
|
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -213,20 +207,16 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public List<GpodnetDevice> getDevices(@NonNull String username)
|
public List<GpodnetDevice> getDevices() throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
requireLoggedIn();
|
||||||
try {
|
try {
|
||||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
URL url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/devices/%s.json", username), null).toURL();
|
||||||
"/api/2/devices/%s.json", username), null).toURL();
|
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
JSONArray devicesArray = new JSONArray(response);
|
JSONArray devicesArray = new JSONArray(response);
|
||||||
return readDeviceListFromJSONArray(devicesArray);
|
return readDeviceListFromJsonArray(devicesArray);
|
||||||
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new GpodnetServiceException(e);
|
||||||
@ -238,19 +228,14 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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.
|
* @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.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public void configureDevice(@NonNull String username,
|
public void configureDevice(@NonNull String deviceId, String caption, GpodnetDevice.DeviceType type)
|
||||||
@NonNull String deviceId,
|
|
||||||
String caption,
|
|
||||||
GpodnetDevice.DeviceType type)
|
|
||||||
throws GpodnetServiceException {
|
throws GpodnetServiceException {
|
||||||
|
requireLoggedIn();
|
||||||
try {
|
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();
|
"/api/2/devices/%s/%s.json", username, deviceId), null).toURL();
|
||||||
String content;
|
String content;
|
||||||
if (caption != null || type != null) {
|
if (caption != null || type != null) {
|
||||||
@ -279,18 +264,14 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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.
|
* @param deviceId The ID of the device whose subscriptions should be returned.
|
||||||
* @return A list of subscriptions in OPML format.
|
* @return A list of subscriptions in OPML format.
|
||||||
* @throws IllegalArgumentException If username or deviceId is null.
|
|
||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public String getSubscriptionsOfDevice(@NonNull String username,
|
public String getSubscriptionsOfDevice(@NonNull String deviceId) throws GpodnetServiceException {
|
||||||
@NonNull String deviceId)
|
requireLoggedIn();
|
||||||
throws GpodnetServiceException {
|
|
||||||
try {
|
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();
|
"/subscriptions/%s/%s.opml", username, deviceId), null).toURL();
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
return executeRequest(request);
|
return executeRequest(request);
|
||||||
@ -305,18 +286,14 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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.
|
* @return A list of subscriptions in OPML format.
|
||||||
* @throws IllegalArgumentException If username is null.
|
* @throws IllegalArgumentException If username is null.
|
||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public String getSubscriptionsOfUser(@NonNull String username)
|
public String getSubscriptionsOfUser() throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
requireLoggedIn();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
URL url = new URI(BASE_SCHEME, baseHost, String.format("/subscriptions/%s.opml", username), null).toURL();
|
||||||
"/subscriptions/%s.opml", username), null).toURL();
|
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
return executeRequest(request);
|
return executeRequest(request);
|
||||||
} catch (MalformedURLException | URISyntaxException e) {
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
@ -330,21 +307,17 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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 deviceId The ID of the device whose subscriptions should be updated.
|
||||||
* @param subscriptions A list of feed URLs containing all subscriptions of the
|
* @param subscriptions A list of feed URLs containing all subscriptions of the
|
||||||
* device.
|
* device.
|
||||||
* @throws IllegalArgumentException If username, deviceId or subscriptions is null.
|
* @throws IllegalArgumentException If username, deviceId or subscriptions is null.
|
||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public void uploadSubscriptions(@NonNull String username,
|
public void uploadSubscriptions(@NonNull String deviceId, @NonNull List<String> subscriptions)
|
||||||
@NonNull String deviceId,
|
|
||||||
@NonNull List<String> subscriptions)
|
|
||||||
throws GpodnetServiceException {
|
throws GpodnetServiceException {
|
||||||
|
requireLoggedIn();
|
||||||
try {
|
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();
|
"/subscriptions/%s/%s.txt", username, deviceId), null).toURL();
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (String s : subscriptions) {
|
for (String s : subscriptions) {
|
||||||
@ -366,25 +339,19 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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 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 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
|
* @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.
|
* for details.
|
||||||
* @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
|
* @throws GpodnetServiceException if added or removed contain duplicates or if there
|
||||||
* @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
|
* is an authentication error.
|
||||||
* is an authentication error.
|
|
||||||
*/
|
*/
|
||||||
public GpodnetUploadChangesResponse uploadChanges(@NonNull String username,
|
public GpodnetUploadChangesResponse uploadChanges(@NonNull String deviceId, @NonNull Collection<String> added,
|
||||||
@NonNull String deviceId,
|
@NonNull Collection<String> removed) throws GpodnetServiceException {
|
||||||
@NonNull Collection<String> added,
|
requireLoggedIn();
|
||||||
@NonNull Collection<String> removed)
|
|
||||||
throws GpodnetServiceException {
|
|
||||||
|
|
||||||
try {
|
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();
|
"/api/2/subscriptions/%s/%s.json", username, deviceId), null).toURL();
|
||||||
|
|
||||||
final JSONObject requestObject = new JSONObject();
|
final JSONObject requestObject = new JSONObject();
|
||||||
@ -408,30 +375,25 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* 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
|
* @param deviceId The ID of the device whose subscription changes should be
|
||||||
* downloaded.
|
* downloaded.
|
||||||
* @param timestamp A timestamp that can be used to receive all changes since a
|
* @param timestamp A timestamp that can be used to receive all changes since a
|
||||||
* specific point in time.
|
* specific point in time.
|
||||||
* @throws IllegalArgumentException If username or deviceId is null.
|
|
||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
||||||
*/
|
*/
|
||||||
public GpodnetSubscriptionChange getSubscriptionChanges(@NonNull String username,
|
public SubscriptionChanges getSubscriptionChanges(@NonNull String deviceId, long timestamp)
|
||||||
@NonNull String deviceId,
|
throws GpodnetServiceException {
|
||||||
long timestamp) throws GpodnetServiceException {
|
requireLoggedIn();
|
||||||
|
|
||||||
String params = String.format("since=%d", timestamp);
|
String params = String.format("since=%d", timestamp);
|
||||||
String path = String.format("/api/2/subscriptions/%s/%s.json",
|
String path = String.format("/api/2/subscriptions/%s/%s.json", username, deviceId);
|
||||||
username, deviceId);
|
|
||||||
try {
|
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();
|
null).toURL();
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
|
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
JSONObject changes = new JSONObject(response);
|
JSONObject changes = new JSONObject(response);
|
||||||
return readSubscriptionChangesFromJSONObject(changes);
|
return readSubscriptionChangesFromJsonObject(changes);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
@ -447,26 +409,36 @@ public class GpodnetService {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method requires authentication.
|
* This method requires authentication.
|
||||||
*
|
*
|
||||||
* @param episodeActions Collection of episode actions.
|
* @param episodeActions Collection of episode actions.
|
||||||
* @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse}
|
* @return a GpodnetUploadChangesResponse. See {@link GpodnetUploadChangesResponse}
|
||||||
* for details.
|
* for details.
|
||||||
* @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
|
* @throws GpodnetServiceException if added or removed contain duplicates or if there
|
||||||
* @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
|
* is an authentication error.
|
||||||
* is an authentication error.
|
|
||||||
*/
|
*/
|
||||||
public GpodnetEpisodeActionPostResponse uploadEpisodeActions(@NonNull Collection<GpodnetEpisodeAction> episodeActions)
|
@Override
|
||||||
throws GpodnetServiceException {
|
public UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> episodeActions) throws SyncServiceException {
|
||||||
|
requireLoggedIn();
|
||||||
String username = GpodnetPreferences.getUsername();
|
UploadChangesResponse response = null;
|
||||||
|
for (int i = 0; i < episodeActions.size(); i += UPLOAD_BULK_SIZE) {
|
||||||
|
response = uploadEpisodeActionsPartial(episodeActions,
|
||||||
|
i, Math.min(episodeActions.size(), i + UPLOAD_BULK_SIZE));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UploadChangesResponse uploadEpisodeActionsPartial(List<EpisodeAction> episodeActions, int from, int to)
|
||||||
|
throws SyncServiceException {
|
||||||
try {
|
try {
|
||||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
Log.d(TAG, "Uploading partial actions " + from + " to " + to + " of " + episodeActions.size());
|
||||||
|
URL url = new URI(BASE_SCHEME, baseHost, String.format(
|
||||||
"/api/2/episodes/%s.json", username), null).toURL();
|
"/api/2/episodes/%s.json", username), null).toURL();
|
||||||
|
|
||||||
final JSONArray list = new JSONArray();
|
final JSONArray list = new JSONArray();
|
||||||
for(GpodnetEpisodeAction episodeAction : episodeActions) {
|
for (int i = from; i < to; i++) {
|
||||||
JSONObject obj = episodeAction.writeToJSONObject();
|
EpisodeAction episodeAction = episodeActions.get(i);
|
||||||
if(obj != null) {
|
JSONObject obj = episodeAction.writeToJsonObject();
|
||||||
|
if (obj != null) {
|
||||||
|
obj.put("device", GpodnetPreferences.getDeviceID());
|
||||||
list.put(obj);
|
list.put(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -478,9 +450,8 @@ public class GpodnetService {
|
|||||||
return GpodnetEpisodeActionPostResponse.fromJSONObject(response);
|
return GpodnetEpisodeActionPostResponse.fromJSONObject(response);
|
||||||
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
} catch (JSONException | MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new SyncServiceException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,30 +461,26 @@ public class GpodnetService {
|
|||||||
*
|
*
|
||||||
* @param timestamp A timestamp that can be used to receive all changes since a
|
* @param timestamp A timestamp that can be used to receive all changes since a
|
||||||
* specific point in time.
|
* specific point in time.
|
||||||
* @throws IllegalArgumentException If username or deviceId is null.
|
* @throws SyncServiceException If there is an authentication error.
|
||||||
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
|
|
||||||
*/
|
*/
|
||||||
public GpodnetEpisodeActionGetResponse getEpisodeChanges(long timestamp) throws GpodnetServiceException {
|
@Override
|
||||||
|
public EpisodeActionChanges getEpisodeActionChanges(long timestamp) throws SyncServiceException {
|
||||||
String username = GpodnetPreferences.getUsername();
|
requireLoggedIn();
|
||||||
|
|
||||||
String params = String.format("since=%d", timestamp);
|
String params = String.format("since=%d", timestamp);
|
||||||
String path = String.format("/api/2/episodes/%s.json",
|
String path = String.format("/api/2/episodes/%s.json", username);
|
||||||
username);
|
|
||||||
try {
|
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();
|
||||||
null).toURL();
|
|
||||||
Request.Builder request = new Request.Builder().url(url);
|
Request.Builder request = new Request.Builder().url(url);
|
||||||
|
|
||||||
String response = executeRequest(request);
|
String response = executeRequest(request);
|
||||||
JSONObject json = new JSONObject(response);
|
JSONObject json = new JSONObject(response);
|
||||||
return readEpisodeActionsFromJSONObject(json);
|
return readEpisodeActionsFromJsonObject(json);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new SyncServiceException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -525,33 +492,27 @@ public class GpodnetService {
|
|||||||
*
|
*
|
||||||
* @throws IllegalArgumentException If username or password is null.
|
* @throws IllegalArgumentException If username or password is null.
|
||||||
*/
|
*/
|
||||||
public void authenticate(@NonNull String username,
|
public void authenticate(@NonNull String username, @NonNull String password) throws GpodnetServiceException {
|
||||||
@NonNull String password)
|
|
||||||
throws GpodnetServiceException {
|
|
||||||
URL url;
|
URL url;
|
||||||
try {
|
try {
|
||||||
url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/auth/%s/login.json", username), null).toURL();
|
||||||
"/api/2/auth/%s/login.json", username), null).toURL();
|
|
||||||
} catch (MalformedURLException | URISyntaxException e) {
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new GpodnetServiceException(e);
|
throw new GpodnetServiceException(e);
|
||||||
}
|
}
|
||||||
RequestBody body = RequestBody.create(TEXT, "");
|
RequestBody requestBody = RequestBody.create(TEXT, "");
|
||||||
Request.Builder request = new Request.Builder().url(url).post(body);
|
Request request = new Request.Builder().url(url).post(requestBody).build();
|
||||||
executeRequestWithAuthentication(request, username, password);
|
try {
|
||||||
}
|
String credential = Credentials.basic(username, password, Charsets.UTF_8);
|
||||||
|
Request authRequest = request.newBuilder().header("Authorization", credential).build();
|
||||||
/**
|
Response response = httpClient.newCall(authRequest).execute();
|
||||||
* Shuts down the GpodnetService's HTTP client. The service will be shut down in a separate thread to avoid
|
checkStatusCode(response);
|
||||||
* NetworkOnMainThreadExceptions.
|
response.body().close();
|
||||||
*/
|
this.username = username;
|
||||||
public void shutdown() {
|
} catch (Exception e) {
|
||||||
new Thread() {
|
e.printStackTrace();
|
||||||
@Override
|
throw new GpodnetServiceException(e);
|
||||||
public void run() {
|
}
|
||||||
AntennapodHttpClient.cleanup();
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String executeRequest(@NonNull Request.Builder requestB) throws GpodnetServiceException {
|
private String executeRequest(@NonNull Request.Builder requestB) throws GpodnetServiceException {
|
||||||
@ -576,36 +537,7 @@ public class GpodnetService {
|
|||||||
return responseString;
|
return responseString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String executeRequestWithAuthentication(Request.Builder requestB,
|
private String getStringFromResponseBody(@NonNull ResponseBody body) throws GpodnetServiceException {
|
||||||
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 {
|
|
||||||
ByteArrayOutputStream outputStream;
|
ByteArrayOutputStream outputStream;
|
||||||
int contentLength = (int) body.contentLength();
|
int contentLength = (int) body.contentLength();
|
||||||
if (contentLength > 0) {
|
if (contentLength > 0) {
|
||||||
@ -627,36 +559,31 @@ public class GpodnetService {
|
|||||||
return outputStream.toString();
|
return outputStream.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkStatusCode(@NonNull Response response)
|
private void checkStatusCode(@NonNull Response response) throws GpodnetServiceException {
|
||||||
throws GpodnetServiceException {
|
|
||||||
int responseCode = response.code();
|
int responseCode = response.code();
|
||||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
throw new GpodnetServiceAuthenticationException("Wrong username or password");
|
throw new GpodnetServiceAuthenticationException("Wrong username or password");
|
||||||
} else {
|
} else {
|
||||||
throw new GpodnetServiceBadStatusCodeException("Bad response code: "
|
throw new GpodnetServiceBadStatusCodeException("Bad response code: " + responseCode, responseCode);
|
||||||
+ responseCode, responseCode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<GpodnetPodcast> readPodcastListFromJSONArray(@NonNull JSONArray array)
|
private List<GpodnetPodcast> readPodcastListFromJsonArray(@NonNull JSONArray array) throws JSONException {
|
||||||
throws JSONException {
|
List<GpodnetPodcast> result = new ArrayList<>(array.length());
|
||||||
List<GpodnetPodcast> result = new ArrayList<>(
|
|
||||||
array.length());
|
|
||||||
for (int i = 0; i < array.length(); i++) {
|
for (int i = 0; i < array.length(); i++) {
|
||||||
result.add(readPodcastFromJSONObject(array.getJSONObject(i)));
|
result.add(readPodcastFromJsonObject(array.getJSONObject(i)));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpodnetPodcast readPodcastFromJSONObject(JSONObject object)
|
private GpodnetPodcast readPodcastFromJsonObject(JSONObject object) throws JSONException {
|
||||||
throws JSONException {
|
|
||||||
String url = object.getString("url");
|
String url = object.getString("url");
|
||||||
|
|
||||||
String title;
|
String title;
|
||||||
Object titleObj = object.opt("title");
|
Object titleObj = object.opt("title");
|
||||||
if (titleObj != null && titleObj instanceof String) {
|
if (titleObj instanceof String) {
|
||||||
title = (String) titleObj;
|
title = (String) titleObj;
|
||||||
} else {
|
} else {
|
||||||
title = url;
|
title = url;
|
||||||
@ -664,7 +591,7 @@ public class GpodnetService {
|
|||||||
|
|
||||||
String description;
|
String description;
|
||||||
Object descriptionObj = object.opt("description");
|
Object descriptionObj = object.opt("description");
|
||||||
if (descriptionObj != null && descriptionObj instanceof String) {
|
if (descriptionObj instanceof String) {
|
||||||
description = (String) descriptionObj;
|
description = (String) descriptionObj;
|
||||||
} else {
|
} else {
|
||||||
description = "";
|
description = "";
|
||||||
@ -673,49 +600,38 @@ public class GpodnetService {
|
|||||||
int subscribers = object.getInt("subscribers");
|
int subscribers = object.getInt("subscribers");
|
||||||
|
|
||||||
Object logoUrlObj = object.opt("logo_url");
|
Object logoUrlObj = object.opt("logo_url");
|
||||||
String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj
|
String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj : null;
|
||||||
: null;
|
|
||||||
if (logoUrl == null) {
|
if (logoUrl == null) {
|
||||||
Object scaledLogoUrl = object.opt("scaled_logo_url");
|
Object scaledLogoUrl = object.opt("scaled_logo_url");
|
||||||
if (scaledLogoUrl != null && scaledLogoUrl instanceof String) {
|
if (scaledLogoUrl instanceof String) {
|
||||||
logoUrl = (String) scaledLogoUrl;
|
logoUrl = (String) scaledLogoUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String website = null;
|
String website = null;
|
||||||
Object websiteObj = object.opt("website");
|
Object websiteObj = object.opt("website");
|
||||||
if (websiteObj != null && websiteObj instanceof String) {
|
if (websiteObj instanceof String) {
|
||||||
website = (String) websiteObj;
|
website = (String) websiteObj;
|
||||||
}
|
}
|
||||||
String mygpoLink = object.getString("mygpo_link");
|
String mygpoLink = object.getString("mygpo_link");
|
||||||
|
|
||||||
String author = null;
|
String author = null;
|
||||||
Object authorObj = object.opt("author");
|
Object authorObj = object.opt("author");
|
||||||
if (authorObj != null && authorObj instanceof String) {
|
if (authorObj instanceof String) {
|
||||||
author = (String) authorObj;
|
author = (String) authorObj;
|
||||||
}
|
}
|
||||||
return new GpodnetPodcast(url,
|
return new GpodnetPodcast(url, title, description, subscribers, logoUrl, website, mygpoLink, author);
|
||||||
title,
|
|
||||||
description,
|
|
||||||
subscribers,
|
|
||||||
logoUrl,
|
|
||||||
website,
|
|
||||||
mygpoLink,
|
|
||||||
author);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<GpodnetDevice> readDeviceListFromJSONArray(@NonNull JSONArray array)
|
private List<GpodnetDevice> readDeviceListFromJsonArray(@NonNull JSONArray array) throws JSONException {
|
||||||
throws JSONException {
|
List<GpodnetDevice> result = new ArrayList<>(array.length());
|
||||||
List<GpodnetDevice> result = new ArrayList<>(
|
|
||||||
array.length());
|
|
||||||
for (int i = 0; i < array.length(); i++) {
|
for (int i = 0; i < array.length(); i++) {
|
||||||
result.add(readDeviceFromJSONObject(array.getJSONObject(i)));
|
result.add(readDeviceFromJsonObject(array.getJSONObject(i)));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpodnetDevice readDeviceFromJSONObject(JSONObject object)
|
private GpodnetDevice readDeviceFromJsonObject(JSONObject object) throws JSONException {
|
||||||
throws JSONException {
|
|
||||||
String id = object.getString("id");
|
String id = object.getString("id");
|
||||||
String caption = object.getString("caption");
|
String caption = object.getString("caption");
|
||||||
String type = object.getString("type");
|
String type = object.getString("type");
|
||||||
@ -723,8 +639,8 @@ public class GpodnetService {
|
|||||||
return new GpodnetDevice(id, caption, type, subscriptions);
|
return new GpodnetDevice(id, caption, type, subscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject(
|
private SubscriptionChanges readSubscriptionChangesFromJsonObject(@NonNull JSONObject object)
|
||||||
@NonNull JSONObject object) throws JSONException {
|
throws JSONException {
|
||||||
|
|
||||||
List<String> added = new LinkedList<>();
|
List<String> added = new LinkedList<>();
|
||||||
JSONArray jsonAdded = object.getJSONArray("add");
|
JSONArray jsonAdded = object.getJSONArray("add");
|
||||||
@ -745,24 +661,44 @@ public class GpodnetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long timestamp = object.getLong("timestamp");
|
long timestamp = object.getLong("timestamp");
|
||||||
return new GpodnetSubscriptionChange(added, removed, timestamp);
|
return new SubscriptionChanges(added, removed, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpodnetEpisodeActionGetResponse readEpisodeActionsFromJSONObject(
|
private EpisodeActionChanges readEpisodeActionsFromJsonObject(@NonNull JSONObject object)
|
||||||
@NonNull JSONObject object) throws JSONException {
|
throws JSONException {
|
||||||
|
|
||||||
List<GpodnetEpisodeAction> episodeActions = new ArrayList<>();
|
List<EpisodeAction> episodeActions = new ArrayList<>();
|
||||||
|
|
||||||
long timestamp = object.getLong("timestamp");
|
long timestamp = object.getLong("timestamp");
|
||||||
JSONArray jsonActions = object.getJSONArray("actions");
|
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);
|
JSONObject jsonAction = jsonActions.getJSONObject(i);
|
||||||
GpodnetEpisodeAction episodeAction = GpodnetEpisodeAction.readFromJSONObject(jsonAction);
|
EpisodeAction episodeAction = EpisodeAction.readFromJsonObject(jsonAction);
|
||||||
if(episodeAction != null) {
|
if (episodeAction != null) {
|
||||||
episodeActions.add(episodeAction);
|
episodeActions.add(episodeAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new GpodnetEpisodeActionGetResponse(episodeActions, timestamp);
|
return new EpisodeActionChanges(episodeActions, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void login() throws GpodnetServiceException {
|
||||||
|
authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubscriptionChanges getSubscriptionChanges(long lastSync) throws GpodnetServiceException {
|
||||||
|
return getSubscriptionChanges(GpodnetPreferences.getDeviceID(), lastSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadChangesResponse uploadSubscriptionChanges(List<String> addedFeeds, List<String> removedFeeds)
|
||||||
|
throws GpodnetServiceException {
|
||||||
|
return uploadChanges(GpodnetPreferences.getDeviceID(), addedFeeds, removedFeeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logout() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet;
|
package de.danoeh.antennapod.core.sync.gpoddernet;
|
||||||
|
|
||||||
class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
|
class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.gpoddernet.model;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
@ -1,7 +1,8 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.gpoddernet.model;
|
||||||
|
|
||||||
import androidx.collection.ArrayMap;
|
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.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
@ -10,13 +11,7 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class GpodnetEpisodeActionPostResponse {
|
public class GpodnetEpisodeActionPostResponse extends UploadChangesResponse {
|
||||||
|
|
||||||
/**
|
|
||||||
* timestamp/ID that can be used for requesting changes since this upload.
|
|
||||||
*/
|
|
||||||
public final long timestamp;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URLs that should be updated. The key of the map is the original URL, the value of the map
|
* URLs that should be updated. The key of the map is the original URL, the value of the map
|
||||||
* is the sanitized URL.
|
* is the sanitized URL.
|
||||||
@ -24,7 +19,7 @@ public class GpodnetEpisodeActionPostResponse {
|
|||||||
private final Map<String, String> updatedUrls;
|
private final Map<String, String> updatedUrls;
|
||||||
|
|
||||||
private GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
|
private GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
|
||||||
this.timestamp = timestamp;
|
super(timestamp);
|
||||||
this.updatedUrls = updatedUrls;
|
this.updatedUrls = updatedUrls;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.gpoddernet.model;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
@ -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.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
@ -1,7 +1,9 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.gpoddernet.model;
|
||||||
|
|
||||||
import androidx.collection.ArrayMap;
|
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.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -9,23 +11,17 @@ import org.json.JSONObject;
|
|||||||
import java.util.Map;
|
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 {
|
public class GpodnetUploadChangesResponse extends UploadChangesResponse {
|
||||||
|
|
||||||
/**
|
|
||||||
* timestamp/ID that can be used for requesting changes since this upload.
|
|
||||||
*/
|
|
||||||
public final long timestamp;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URLs that should be updated. The key of the map is the original URL, the value of the map
|
* URLs that should be updated. The key of the map is the original URL, the value of the map
|
||||||
* is the sanitized URL.
|
* is the sanitized URL.
|
||||||
*/
|
*/
|
||||||
private final Map<String, String> updatedUrls;
|
public final Map<String, String> updatedUrls;
|
||||||
|
|
||||||
private GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
|
public GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
|
||||||
this.timestamp = timestamp;
|
super(timestamp);
|
||||||
this.updatedUrls = updatedUrls;
|
this.updatedUrls = updatedUrls;
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.model;
|
||||||
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.util.ObjectsCompat;
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
|
import de.danoeh.antennapod.core.util.DateUtils;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@ -12,104 +14,65 @@ import java.util.Date;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
public class EpisodeAction {
|
||||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
private static final String TAG = "EpisodeAction";
|
||||||
import de.danoeh.antennapod.core.util.DateUtils;
|
public static final Action NEW = Action.NEW;
|
||||||
|
public static final Action DOWNLOAD = Action.DOWNLOAD;
|
||||||
public class GpodnetEpisodeAction {
|
public static final Action PLAY = Action.PLAY;
|
||||||
|
public static final Action DELETE = Action.DELETE;
|
||||||
private static final String TAG = "GpodnetEpisodeAction";
|
|
||||||
|
|
||||||
public enum Action {
|
|
||||||
NEW, DOWNLOAD, PLAY, DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String podcast;
|
private final String podcast;
|
||||||
private final String episode;
|
private final String episode;
|
||||||
private final String deviceId;
|
|
||||||
private final Action action;
|
private final Action action;
|
||||||
private final Date timestamp;
|
private final Date timestamp;
|
||||||
private final int started;
|
private final int started;
|
||||||
private final int position;
|
private final int position;
|
||||||
private final int total;
|
private final int total;
|
||||||
|
|
||||||
private GpodnetEpisodeAction(Builder builder) {
|
private EpisodeAction(Builder builder) {
|
||||||
this.podcast = builder.podcast;
|
this.podcast = builder.podcast;
|
||||||
this.episode = builder.episode;
|
this.episode = builder.episode;
|
||||||
this.action = builder.action;
|
this.action = builder.action;
|
||||||
this.deviceId = builder.deviceId;
|
|
||||||
this.timestamp = builder.timestamp;
|
this.timestamp = builder.timestamp;
|
||||||
this.started = builder.started;
|
this.started = builder.started;
|
||||||
this.position = builder.position;
|
this.position = builder.position;
|
||||||
this.total = builder.total;
|
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",
|
* Create an episode action object from JSON representation. Mandatory fields are "podcast",
|
||||||
* "episode" and "action".
|
* "episode" and "action".
|
||||||
*
|
*
|
||||||
* @param object JSON representation
|
* @param object JSON representation
|
||||||
* @return episode action object, or null if mandatory values are missing
|
* @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 podcast = object.optString("podcast", null);
|
||||||
String episode = object.optString("episode", null);
|
String episode = object.optString("episode", null);
|
||||||
String actionString = object.optString("action", 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;
|
return null;
|
||||||
}
|
}
|
||||||
GpodnetEpisodeAction.Action action;
|
EpisodeAction.Action action;
|
||||||
try {
|
try {
|
||||||
action = GpodnetEpisodeAction.Action.valueOf(actionString.toUpperCase());
|
action = EpisodeAction.Action.valueOf(actionString.toUpperCase());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String deviceId = object.optString("device", "");
|
EpisodeAction.Builder builder = new EpisodeAction.Builder(podcast, episode, action);
|
||||||
GpodnetEpisodeAction.Builder builder = new GpodnetEpisodeAction.Builder(podcast, episode, action)
|
|
||||||
.deviceId(deviceId);
|
|
||||||
String utcTimestamp = object.optString("timestamp", null);
|
String utcTimestamp = object.optString("timestamp", null);
|
||||||
if(!TextUtils.isEmpty(utcTimestamp)) {
|
if (!TextUtils.isEmpty(utcTimestamp)) {
|
||||||
builder.timestamp(DateUtils.parse(utcTimestamp));
|
builder.timestamp(DateUtils.parse(utcTimestamp));
|
||||||
}
|
}
|
||||||
if(action == GpodnetEpisodeAction.Action.PLAY) {
|
if (action == EpisodeAction.Action.PLAY) {
|
||||||
int started = object.optInt("started", -1);
|
int started = object.optInt("started", -1);
|
||||||
int position = object.optInt("position", -1);
|
int position = object.optInt("position", -1);
|
||||||
int total = object.optInt("total", -1);
|
int total = object.optInt("total", -1);
|
||||||
if(started >= 0 && position > 0 && total > 0) {
|
if (started >= 0 && position > 0 && total > 0) {
|
||||||
builder
|
builder
|
||||||
.started(started)
|
.started(started)
|
||||||
.position(position)
|
.position(position)
|
||||||
.total(total);
|
.total(total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
@ -123,10 +86,6 @@ public class GpodnetEpisodeAction {
|
|||||||
return this.episode;
|
return this.episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceId() {
|
|
||||||
return this.deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action getAction() {
|
public Action getAction() {
|
||||||
return this.action;
|
return this.action;
|
||||||
}
|
}
|
||||||
@ -140,7 +99,7 @@ public class GpodnetEpisodeAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the position (in seconds) at which the client started playback
|
* Returns the position (in seconds) at which the client started playback.
|
||||||
*
|
*
|
||||||
* @return start position (in seconds)
|
* @return start position (in seconds)
|
||||||
*/
|
*/
|
||||||
@ -149,7 +108,7 @@ public class GpodnetEpisodeAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the position (in seconds) at which the client stopped playback
|
* Returns the position (in seconds) at which the client stopped playback.
|
||||||
*
|
*
|
||||||
* @return stop position (in seconds)
|
* @return stop position (in seconds)
|
||||||
*/
|
*/
|
||||||
@ -168,28 +127,24 @@ public class GpodnetEpisodeAction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) {
|
||||||
if (o == null || !(o instanceof GpodnetEpisodeAction)) return false;
|
return true;
|
||||||
|
}
|
||||||
GpodnetEpisodeAction that = (GpodnetEpisodeAction) o;
|
if (!(o instanceof EpisodeAction)) {
|
||||||
|
|
||||||
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;
|
return false;
|
||||||
if (action != that.action) return false;
|
}
|
||||||
return !(timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null);
|
|
||||||
|
|
||||||
|
EpisodeAction that = (EpisodeAction) o;
|
||||||
|
return started == that.started && position == that.position && total == that.total && action != that.action
|
||||||
|
&& ObjectsCompat.equals(podcast, that.podcast)
|
||||||
|
&& ObjectsCompat.equals(episode, that.episode)
|
||||||
|
&& ObjectsCompat.equals(timestamp, that.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = podcast != null ? podcast.hashCode() : 0;
|
int result = podcast != null ? podcast.hashCode() : 0;
|
||||||
result = 31 * result + (episode != null ? episode.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 + (action != null ? action.hashCode() : 0);
|
||||||
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
|
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
|
||||||
result = 31 * result + started;
|
result = 31 * result + started;
|
||||||
@ -198,38 +153,26 @@ public class GpodnetEpisodeAction {
|
|||||||
return result;
|
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
|
* Returns a JSON object representation of this object.
|
||||||
*
|
*
|
||||||
* @return JSON object representation, or null if the object is invalid
|
* @return JSON object representation, or null if the object is invalid
|
||||||
*/
|
*/
|
||||||
public JSONObject writeToJSONObject() {
|
public JSONObject writeToJsonObject() {
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
try {
|
try {
|
||||||
obj.putOpt("podcast", this.podcast);
|
obj.putOpt("podcast", this.podcast);
|
||||||
obj.putOpt("episode", this.episode);
|
obj.putOpt("episode", this.episode);
|
||||||
obj.put("device", this.deviceId);
|
|
||||||
obj.put("action", this.getActionString());
|
obj.put("action", this.getActionString());
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
|
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
|
||||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
obj.put("timestamp",formatter.format(this.timestamp));
|
obj.put("timestamp", formatter.format(this.timestamp));
|
||||||
if (this.getAction() == Action.PLAY) {
|
if (this.getAction() == Action.PLAY) {
|
||||||
obj.put("started", this.started);
|
obj.put("started", this.started);
|
||||||
obj.put("position", this.position);
|
obj.put("position", this.position);
|
||||||
obj.put("total", this.total);
|
obj.put("total", this.total);
|
||||||
}
|
}
|
||||||
} catch(JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e(TAG, "writeToJSONObject(): " + e.getMessage());
|
Log.e(TAG, "writeToJSONObject(): " + e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -238,16 +181,19 @@ public class GpodnetEpisodeAction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "GpodnetEpisodeAction{" +
|
return "EpisodeAction{"
|
||||||
"podcast='" + podcast + '\'' +
|
+ "podcast='" + podcast + '\''
|
||||||
", episode='" + episode + '\'' +
|
+ ", episode='" + episode + '\''
|
||||||
", deviceId='" + deviceId + '\'' +
|
+ ", action=" + action
|
||||||
", action=" + action +
|
+ ", timestamp=" + timestamp
|
||||||
", timestamp=" + timestamp +
|
+ ", started=" + started
|
||||||
", started=" + started +
|
+ ", position=" + position
|
||||||
", position=" + position +
|
+ ", total=" + total
|
||||||
", total=" + total +
|
+ '}';
|
||||||
'}';
|
}
|
||||||
|
|
||||||
|
public enum Action {
|
||||||
|
NEW, DOWNLOAD, PLAY, DELETE
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
@ -258,7 +204,6 @@ public class GpodnetEpisodeAction {
|
|||||||
private final Action action;
|
private final Action action;
|
||||||
|
|
||||||
// optional
|
// optional
|
||||||
private String deviceId = "";
|
|
||||||
private Date timestamp;
|
private Date timestamp;
|
||||||
private int started = -1;
|
private int started = -1;
|
||||||
private int position = -1;
|
private int position = -1;
|
||||||
@ -274,15 +219,6 @@ public class GpodnetEpisodeAction {
|
|||||||
this.action = action;
|
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) {
|
public Builder timestamp(Date timestamp) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
return this;
|
return this;
|
||||||
@ -293,28 +229,28 @@ public class GpodnetEpisodeAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Builder started(int seconds) {
|
public Builder started(int seconds) {
|
||||||
if(action == Action.PLAY) {
|
if (action == Action.PLAY) {
|
||||||
this.started = seconds;
|
this.started = seconds;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder position(int seconds) {
|
public Builder position(int seconds) {
|
||||||
if(action == Action.PLAY) {
|
if (action == Action.PLAY) {
|
||||||
this.position = seconds;
|
this.position = seconds;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder total(int seconds) {
|
public Builder total(int seconds) {
|
||||||
if(action == Action.PLAY) {
|
if (action == Action.PLAY) {
|
||||||
this.total = seconds;
|
this.total = seconds;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpodnetEpisodeAction build() {
|
public EpisodeAction build() {
|
||||||
return new GpodnetEpisodeAction(this);
|
return new EpisodeAction(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package de.danoeh.antennapod.core.sync.model;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EpisodeActionChanges {
|
||||||
|
|
||||||
|
private final List<EpisodeAction> episodeActions;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
public EpisodeActionChanges(@NonNull List<EpisodeAction> episodeActions, long timestamp) {
|
||||||
|
this.episodeActions = episodeActions;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EpisodeAction> getEpisodeActions() {
|
||||||
|
return this.episodeActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return this.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EpisodeActionGetResponse{"
|
||||||
|
+ "episodeActions=" + episodeActions
|
||||||
|
+ ", timestamp=" + timestamp
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package de.danoeh.antennapod.core.sync.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ISyncService {
|
||||||
|
|
||||||
|
void login() throws SyncServiceException;
|
||||||
|
|
||||||
|
SubscriptionChanges getSubscriptionChanges(long lastSync) throws SyncServiceException;
|
||||||
|
|
||||||
|
UploadChangesResponse uploadSubscriptionChanges(
|
||||||
|
List<String> addedFeeds, List<String> removedFeeds) throws SyncServiceException;
|
||||||
|
|
||||||
|
EpisodeActionChanges getEpisodeActionChanges(long lastSync) throws SyncServiceException;
|
||||||
|
|
||||||
|
UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> queuedEpisodeActions)
|
||||||
|
throws SyncServiceException;
|
||||||
|
|
||||||
|
void logout() throws SyncServiceException;
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
package de.danoeh.antennapod.core.sync.model;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GpodnetSubscriptionChange {
|
public class SubscriptionChanges {
|
||||||
private final List<String> added;
|
private final List<String> added;
|
||||||
private final List<String> removed;
|
private final List<String> removed;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
public GpodnetSubscriptionChange(@NonNull List<String> added,
|
public SubscriptionChanges(@NonNull List<String> added,
|
||||||
@NonNull List<String> removed,
|
@NonNull List<String> removed,
|
||||||
long timestamp) {
|
long timestamp) {
|
||||||
this.added = added;
|
this.added = added;
|
||||||
this.removed = removed;
|
this.removed = removed;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@ -19,7 +19,7 @@ public class GpodnetSubscriptionChange {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "GpodnetSubscriptionChange [added=" + added.toString()
|
return "SubscriptionChange [added=" + added.toString()
|
||||||
+ ", removed=" + removed.toString() + ", timestamp="
|
+ ", removed=" + removed.toString() + ", timestamp="
|
||||||
+ timestamp + "]";
|
+ timestamp + "]";
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
BIN
core/src/main/res/drawable-nodpi/gpodder_icon.png
Normal file
BIN
core/src/main/res/drawable-nodpi/gpodder_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
@ -437,12 +437,10 @@
|
|||||||
<string name="pref_gpodnet_logout_toast">Logout was successful</string>
|
<string name="pref_gpodnet_logout_toast">Logout was successful</string>
|
||||||
<string name="pref_gpodnet_setlogin_information_title">Change login information</string>
|
<string name="pref_gpodnet_setlogin_information_title">Change login information</string>
|
||||||
<string name="pref_gpodnet_setlogin_information_sum">Change the login information for your gpodder.net account.</string>
|
<string name="pref_gpodnet_setlogin_information_sum">Change the login information for your gpodder.net account.</string>
|
||||||
<string name="pref_gpodnet_sync_changes_title">Sync changes now</string>
|
<string name="pref_gpodnet_sync_changes_title">Synchronize now</string>
|
||||||
<string name="pref_gpodnet_sync_changes_sum">Sync subscription and episode state changes with gpodder.net.</string>
|
<string name="pref_gpodnet_sync_changes_sum">Sync subscription and episode state changes with gpodder.net.</string>
|
||||||
<string name="pref_gpodnet_full_sync_title">Full sync now</string>
|
<string name="pref_gpodnet_full_sync_title">Force full synchronization</string>
|
||||||
<string name="pref_gpodnet_full_sync_sum">Sync all subscriptions and episode states with gpodder.net.</string>
|
<string name="pref_gpodnet_full_sync_sum">Sync all subscriptions and episode states with gpodder.net.</string>
|
||||||
<string name="pref_gpodnet_sync_sum_last_sync_line">Last sync attempt: %1$s (%2$s)</string>
|
|
||||||
<string name="pref_gpodnet_sync_started">Sync started</string>
|
|
||||||
<string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
|
<string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
|
||||||
<string name="pref_gpodnet_notifications_title">Show sync error notifications</string>
|
<string name="pref_gpodnet_notifications_title">Show sync error notifications</string>
|
||||||
<string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
|
<string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
|
||||||
@ -536,6 +534,15 @@
|
|||||||
<string name="search_label">Search</string>
|
<string name="search_label">Search</string>
|
||||||
<string name="no_results_for_query">No results were found for \"%1$s\"</string>
|
<string name="no_results_for_query">No results were found for \"%1$s\"</string>
|
||||||
|
|
||||||
|
<!-- Synchronization -->
|
||||||
|
<string name="sync_status_started">Sync started</string>
|
||||||
|
<string name="sync_status_episodes_upload">Uploading episode changes…</string>
|
||||||
|
<string name="sync_status_episodes_download">Downloading episode changes…</string>
|
||||||
|
<string name="sync_status_upload_played">Uploading played status…</string>
|
||||||
|
<string name="sync_status_subscriptions">Synchronizing subscriptions…</string>
|
||||||
|
<string name="sync_status_success">Synchronization successful</string>
|
||||||
|
<string name="sync_status_error">Synchronization failed</string>
|
||||||
|
|
||||||
<!-- import and export -->
|
<!-- import and export -->
|
||||||
<string name="import_export_summary">Move subscriptions and queue to another device</string>
|
<string name="import_export_summary">Move subscriptions and queue to another device</string>
|
||||||
<string name="database">Database</string>
|
<string name="database">Database</string>
|
||||||
|
@ -39,8 +39,6 @@ public class ClientConfig {
|
|||||||
|
|
||||||
public static PlaybackServiceCallbacks playbackServiceCallbacks;
|
public static PlaybackServiceCallbacks playbackServiceCallbacks;
|
||||||
|
|
||||||
public static GpodnetCallbacks gpodnetCallbacks;
|
|
||||||
|
|
||||||
public static DBTasksCallbacks dbTasksCallbacks;
|
public static DBTasksCallbacks dbTasksCallbacks;
|
||||||
|
|
||||||
public static CastCallbacks castCallbacks;
|
public static CastCallbacks castCallbacks;
|
||||||
|
@ -206,7 +206,7 @@ public class CastUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
FeedItem feedItem = DBReader.getFeedItem(metadata.getString(KEY_FEED_URL),
|
FeedItem feedItem = DBReader.getFeedItemByUrl(metadata.getString(KEY_FEED_URL),
|
||||||
metadata.getString(KEY_EPISODE_IDENTIFIER));
|
metadata.getString(KEY_EPISODE_IDENTIFIER));
|
||||||
if (feedItem != null) {
|
if (feedItem != null) {
|
||||||
result = feedItem.getMedia();
|
result = feedItem.getMedia();
|
||||||
|
@ -1,2 +1 @@
|
|||||||
include ':app'
|
include ':app', ':sync', ':core'
|
||||||
include ':core'
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user