Moved synchronization to its own module

This commit is contained in:
ByteHamster 2021-04-24 16:18:02 +02:00
parent 7b5f29e090
commit e30533a810
41 changed files with 262 additions and 121 deletions

View File

@ -164,6 +164,8 @@ android {
dependencies {
implementation project(":core")
implementation project(':model')
implementation project(':net:sync:gpoddernet')
implementation project(':net:sync:model')
implementation project(':ui:app-start-intent')
implementation project(':ui:common')

View File

@ -6,10 +6,10 @@ import java.util.List;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -28,14 +28,16 @@ public class GPodnetServiceTest {
private static final String USER = "";
private static final String PW = "";
private static final String DEVICE_ID = "radio";
@Before
public void setUp() {
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST);
service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetService.DEFAULT_BASE_HOST, DEVICE_ID, USER, PW);
}
private void authenticate() throws GpodnetServiceException {
service.authenticate(USER, PW);
service.login();
}
@Test
@ -43,7 +45,7 @@ public class GPodnetServiceTest {
authenticate();
ArrayList<String> l = new ArrayList<>();
l.add("http://bitsundso.de/feed");
service.uploadSubscriptions("radio", l);
service.uploadSubscriptions(DEVICE_ID, l);
}
@Test
@ -52,7 +54,7 @@ public class GPodnetServiceTest {
ArrayList<String> l = new ArrayList<>();
l.add("http://bitsundso.de/feed");
l.add("http://gamesundso.de/feed");
service.uploadSubscriptions("radio", l);
service.uploadSubscriptions(DEVICE_ID, l);
}
@Test
@ -62,14 +64,14 @@ public class GPodnetServiceTest {
List<String> subscriptions = Arrays.asList(URLS[0], URLS[1]);
List<String> removed = singletonList(URLS[0]);
List<String> added = Arrays.asList(URLS[2], URLS[3]);
service.uploadSubscriptions("radio", subscriptions);
service.uploadChanges("radio", added, removed);
service.uploadSubscriptions(DEVICE_ID, subscriptions);
service.uploadSubscriptionChanges(added, removed);
}
@Test
public void testGetSubscriptionChanges() throws GpodnetServiceException {
authenticate();
service.getSubscriptionChanges("radio", 1362322610L);
service.getSubscriptionChanges(1362322610L);
}
@Test
@ -83,7 +85,7 @@ public class GPodnetServiceTest {
public void testGetSubscriptionsOfDevice()
throws GpodnetServiceException {
authenticate();
service.getSubscriptionsOfDevice("radio");
service.getSubscriptionsOfDevice(DEVICE_ID);
}
@Test

View File

@ -13,7 +13,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import org.apache.commons.lang3.StringUtils;

View File

@ -8,7 +8,7 @@ import android.widget.ArrayAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag;
import java.util.List;

View File

@ -2,9 +2,9 @@ package de.danoeh.antennapod.discovery;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -18,7 +18,8 @@ public class GpodnetPodcastSearcher implements PodcastSearcher {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
try {
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl());
GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(),
GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
List<PodcastSearchResult> results = new ArrayList<>();
for (GpodnetPodcast podcast : gpodnetPodcasts) {

View File

@ -1,7 +1,7 @@
package de.danoeh.antennapod.discovery;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import de.mfietz.fyydlin.SearchHit;
import org.json.JSONArray;
import org.json.JSONException;

View File

@ -17,9 +17,9 @@ import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -76,7 +76,8 @@ public abstract class PodcastListFragment extends Fragment {
disposable = Observable.fromCallable(
() -> {
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl());
GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(),
GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
return loadPodcastData(service);
})
.subscribeOn(Schedulers.io())

View File

@ -1,8 +1,8 @@
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 de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import java.util.List;

View File

@ -4,9 +4,9 @@ import java.util.Collections;
import java.util.List;
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;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
/**
* Displays suggestions from gpodder.net
@ -17,7 +17,7 @@ public class SuggestionListFragment extends PodcastListFragment {
@Override
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
if (GpodnetPreferences.loggedIn()) {
service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
service.login();
return service.getSuggestions(SUGGESTIONS_COUNT);
} else {
return Collections.emptyList();

View File

@ -5,10 +5,10 @@ import androidx.annotation.NonNull;
import java.util.List;
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 de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag;
/**
* Shows all podcasts from gpodder.net that belong to a specific tag.

View File

@ -10,8 +10,8 @@ import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -51,7 +51,8 @@ public class TagListFragment extends ListFragment {
disposable = Observable.fromCallable(
() -> {
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl());
GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(),
GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
return service.getTopTags(COUNT);
})
.subscribeOn(Schedulers.io())

View File

@ -25,8 +25,8 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
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.model.GpodnetDevice;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.core.util.FileNameGenerator;
import de.danoeh.antennapod.core.util.IntentUtils;
import io.reactivex.Completable;
@ -96,7 +96,9 @@ public class GpodderAuthenticationFragment extends DialogFragment {
} else {
GpodnetPreferences.setHosturl(GpodnetService.DEFAULT_BASE_HOST);
}
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHosturl());
service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(),
GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
getDialog().setTitle(GpodnetPreferences.getHosturl());
advance();
});
@ -138,7 +140,8 @@ public class GpodderAuthenticationFragment extends DialogFragment {
inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
Completable.fromAction(() -> {
service.authenticate(usernameStr, passwordStr);
service.setCredentials(usernameStr, passwordStr);
service.login();
devices = service.getDevices();
GpodderAuthenticationFragment.this.username = usernameStr;
GpodderAuthenticationFragment.this.password = passwordStr;

View File

@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.model.EpisodeAction;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ShareUtils;

View File

@ -75,6 +75,8 @@ android {
dependencies {
implementation project(':model')
implementation project(':net:ssl')
implementation project(':net:sync:gpoddernet')
implementation project(':net:sync:model')
implementation project(':ui:app-start-intent')
implementation project(':ui:common')
implementation project(':ui:png-icons')

View File

@ -6,7 +6,7 @@ import android.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
/**
* Manages preferences for accessing gpodder.net service

View File

@ -19,7 +19,7 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.model.EpisodeAction;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import org.greenrobot.eventbus.EventBus;
/**

View File

@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.model.EpisodeAction;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import org.greenrobot.eventbus.EventBus;
import java.io.File;

View File

@ -32,17 +32,17 @@ 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.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.net.sync.model.EpisodeActionChanges;
import de.danoeh.antennapod.net.sync.model.ISyncService;
import de.danoeh.antennapod.net.sync.model.SubscriptionChanges;
import de.danoeh.antennapod.net.sync.model.SyncServiceException;
import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import org.apache.commons.lang3.StringUtils;
@ -81,7 +81,9 @@ public class SyncService extends Worker {
if (!GpodnetPreferences.loggedIn()) {
return Result.success();
}
syncServiceImpl = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHosturl());
syncServiceImpl = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(),
GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
SharedPreferences.Editor prefs = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit();
prefs.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, System.currentTimeMillis()).apply();

3
net/sync/README.md Normal file
View File

@ -0,0 +1,3 @@
# :net:sync
This folder contains modules related to external services for synchronization. The module `model` provides the basic interfaces for implementing a synchronization backend. The other modules contains backends for specific synchronization services.

View File

@ -0,0 +1,3 @@
# :net:sync:gpoddernet
This module contains the sync backend for the open-source podcast synchronization service "Gpodder.net".

View File

@ -0,0 +1,57 @@
apply plugin: "com.android.library"
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled false
testApplicationId "de.danoeh.antennapod.core.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android.txt")
}
debug {
// debug build has method count over 64k single-dex threshold.
// For building debug build to use on Android < 21 (pre-Android 5) devices,
// you need to manually change class
// de.danoeh.antennapod.PodcastApp to extend MultiDexApplication .
// See Issue #2813
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
lintOptions {
disable 'GradleDependency'
checkDependencies true
warningsAsErrors true
abortOnError true
}
}
dependencies {
implementation project(':net:sync:model')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
}

View File

@ -0,0 +1 @@
<manifest package="de.danoeh.antennapod.net.sync.gpoddernet" />

View File

@ -1,20 +1,18 @@
package de.danoeh.antennapod.core.sync.gpoddernet;
package de.danoeh.antennapod.net.sync.gpoddernet;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.BuildConfig;
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 de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.net.sync.model.EpisodeActionChanges;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetEpisodeActionPostResponse;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.net.sync.model.ISyncService;
import de.danoeh.antennapod.net.sync.model.SubscriptionChanges;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetUploadChangesResponse;
import de.danoeh.antennapod.net.sync.model.SyncServiceException;
import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
import okhttp3.Credentials;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@ -36,7 +34,6 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@ -55,17 +52,24 @@ public class GpodnetService implements ISyncService {
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 String baseScheme;
private String baseHost;
private int basePort;
private final String baseHost;
private final String deviceId;
private String username;
private String password;
private boolean loggedIn = false;
private final OkHttpClient httpClient;
private String username = null;
// split into schema, host and port - missing parts are null
private static Pattern urlsplit_regex = Pattern.compile("(?:(https?)://)?([^:]+)(?::(\\d+))?");
public GpodnetService(OkHttpClient httpClient, String baseHosturl) {
public GpodnetService(OkHttpClient httpClient, String baseHosturl,
String deviceId, String username, String password) {
this.httpClient = httpClient;
this.deviceId = deviceId;
this.username = username;
this.password = password;
Matcher m = urlsplit_regex.matcher(baseHosturl);
if (m.matches()) {
@ -97,7 +101,7 @@ public class GpodnetService implements ISyncService {
}
private void requireLoggedIn() {
if (username == null) {
if (!loggedIn) {
throw new IllegalStateException("Not logged in");
}
}
@ -454,7 +458,6 @@ public class GpodnetService implements ISyncService {
* <p/>
* This method requires authentication.
*
* @param deviceId The ID of the device whose subscriptions should be updated.
* @param added Collection of feed URLs of added feeds. This Collection MUST NOT contain any duplicates
* @param removed Collection of feed URLs of removed feeds. This Collection MUST NOT contain any duplicates
* @return a GpodnetUploadChangesResponse. See {@link GpodnetUploadChangesResponse}
@ -462,8 +465,9 @@ public class GpodnetService implements ISyncService {
* @throws GpodnetServiceException if added or removed contain duplicates or if there
* is an authentication error.
*/
public GpodnetUploadChangesResponse uploadChanges(@NonNull String deviceId, @NonNull Collection<String> added,
@NonNull Collection<String> removed) throws GpodnetServiceException {
@Override
public UploadChangesResponse uploadSubscriptionChanges(List<String> added, List<String> removed)
throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(baseScheme, null, baseHost, basePort,
@ -490,14 +494,12 @@ public class GpodnetService implements ISyncService {
* <p/>
* This method requires authentication.
*
* @param deviceId The ID of the device whose subscription changes should be
* downloaded.
* @param timestamp A timestamp that can be used to receive all changes since a
* specific point in time.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public SubscriptionChanges getSubscriptionChanges(@NonNull String deviceId, long timestamp)
throws GpodnetServiceException {
@Override
public SubscriptionChanges getSubscriptionChanges(long timestamp) throws GpodnetServiceException {
requireLoggedIn();
String params = String.format(Locale.US, "since=%d", timestamp);
String path = String.format("/api/2/subscriptions/%s/%s.json", username, deviceId);
@ -552,7 +554,7 @@ public class GpodnetService implements ISyncService {
EpisodeAction episodeAction = episodeActions.get(i);
JSONObject obj = episodeAction.writeToJsonObject();
if (obj != null) {
obj.put("device", GpodnetPreferences.getDeviceID());
obj.put("device", deviceId);
list.put(obj);
}
}
@ -606,7 +608,8 @@ public class GpodnetService implements ISyncService {
*
* @throws IllegalArgumentException If username or password is null.
*/
public void authenticate(@NonNull String username, @NonNull String password) throws GpodnetServiceException {
@Override
public void login() throws GpodnetServiceException {
URL url;
try {
url = new URI(baseScheme, null, baseHost, basePort,
@ -623,7 +626,7 @@ public class GpodnetService implements ISyncService {
Response response = httpClient.newCall(authRequest).execute();
checkStatusCode(response);
response.body().close();
this.username = username;
this.loggedIn = true;
} catch (Exception e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
@ -803,24 +806,13 @@ public class GpodnetService implements ISyncService {
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() {
}
public void setCredentials(String username, String password) {
this.username = username;
this.password = password;
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.gpoddernet;
package de.danoeh.antennapod.net.sync.gpoddernet;
public class GpodnetServiceAuthenticationException extends GpodnetServiceException {
private static final long serialVersionUID = 1L;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.gpoddernet;
package de.danoeh.antennapod.net.sync.gpoddernet;
class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
private static final long serialVersionUID = 1L;

View File

@ -1,6 +1,6 @@
package de.danoeh.antennapod.core.sync.gpoddernet;
package de.danoeh.antennapod.net.sync.gpoddernet;
import de.danoeh.antennapod.core.sync.model.SyncServiceException;
import de.danoeh.antennapod.net.sync.model.SyncServiceException;
public class GpodnetServiceException extends SyncServiceException {
private static final long serialVersionUID = 1L;

View File

@ -1,7 +1,9 @@
package de.danoeh.antennapod.core.sync.gpoddernet.model;
package de.danoeh.antennapod.net.sync.gpoddernet.model;
import androidx.annotation.NonNull;
import java.util.Locale;
public class GpodnetDevice {
private final String id;
@ -49,7 +51,7 @@ public class GpodnetDevice {
@Override
public String toString() {
return super.toString().toLowerCase();
return super.toString().toLowerCase(Locale.US);
}
}

View File

@ -1,8 +1,8 @@
package de.danoeh.antennapod.core.sync.gpoddernet.model;
package de.danoeh.antennapod.net.sync.gpoddernet.model;
import androidx.collection.ArrayMap;
import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.json.JSONArray;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.gpoddernet.model;
package de.danoeh.antennapod.net.sync.gpoddernet.model;
import androidx.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.gpoddernet.model;
package de.danoeh.antennapod.net.sync.gpoddernet.model;
import android.os.Parcel;
import android.os.Parcelable;

View File

@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.sync.gpoddernet.model;
package de.danoeh.antennapod.net.sync.gpoddernet.model;
import androidx.collection.ArrayMap;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

3
net/sync/model/README.md Normal file
View File

@ -0,0 +1,3 @@
# :net:sync:model
This module contains the basic interfaces for implementing a sync backend.

View File

@ -0,0 +1,54 @@
apply plugin: "com.android.library"
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled false
testApplicationId "de.danoeh.antennapod.core.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android.txt")
}
debug {
// debug build has method count over 64k single-dex threshold.
// For building debug build to use on Android < 21 (pre-Android 5) devices,
// you need to manually change class
// de.danoeh.antennapod.PodcastApp to extend MultiDexApplication .
// See Issue #2813
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
lintOptions {
disable 'GradleDependency'
checkDependencies true
warningsAsErrors true
abortOnError true
}
}
dependencies {
implementation project(':model')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
}

View File

@ -0,0 +1 @@
<manifest package="de.danoeh.antennapod.net.sync.model" />

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
import android.text.TextUtils;
import android.util.Log;
@ -6,10 +6,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.util.ObjectsCompat;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.util.DateUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@ -17,6 +17,7 @@ import java.util.TimeZone;
public class EpisodeAction {
private static final String TAG = "EpisodeAction";
private static final String PATTERN_ISO_DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ss";
public static final Action NEW = Action.NEW;
public static final Action DOWNLOAD = Action.DOWNLOAD;
public static final Action PLAY = Action.PLAY;
@ -56,14 +57,20 @@ public class EpisodeAction {
}
EpisodeAction.Action action;
try {
action = EpisodeAction.Action.valueOf(actionString.toUpperCase());
action = EpisodeAction.Action.valueOf(actionString.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
return null;
}
EpisodeAction.Builder builder = new EpisodeAction.Builder(podcast, episode, action);
String utcTimestamp = object.optString("timestamp", null);
if (!TextUtils.isEmpty(utcTimestamp)) {
builder.timestamp(DateUtils.parse(utcTimestamp));
try {
SimpleDateFormat parser = new SimpleDateFormat(PATTERN_ISO_DATEFORMAT, Locale.US);
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
builder.timestamp(parser.parse(utcTimestamp));
} catch (ParseException e) {
e.printStackTrace();
}
}
if (action == EpisodeAction.Action.PLAY) {
int started = object.optInt("started", -1);
@ -92,7 +99,7 @@ public class EpisodeAction {
}
private String getActionString() {
return this.action.name().toLowerCase();
return this.action.name().toLowerCase(Locale.US);
}
public Date getTimestamp() {

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
import androidx.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
import java.util.List;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
import androidx.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
public class SyncServiceException extends Exception {
private static final long serialVersionUID = 1L;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.sync.model;
package de.danoeh.antennapod.net.sync.model;
public abstract class UploadChangesResponse {

View File

@ -1,7 +1,11 @@
include ':app'
include ':core'
include ':model'
include ':net:ssl'
include ':net:sync:gpoddernet'
include ':net:sync:model'
include ':ui:app-start-intent'
include ':ui:common'
include ':ui:png-icons'