diff options
Diffstat (limited to 'net/sync/service-interface')
14 files changed, 437 insertions, 54 deletions
diff --git a/net/sync/service-interface/build.gradle b/net/sync/service-interface/build.gradle index c1a559da3..fd170b36a 100644 --- a/net/sync/service-interface/build.gradle +++ b/net/sync/service-interface/build.gradle @@ -9,9 +9,7 @@ android { dependencies { implementation project(':model') - implementation project(':net:sync:model') implementation project(':storage:preferences') - implementation project(':ui:i18n') annotationProcessor "androidx.annotation:annotation:$annotationVersion" implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeAction.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeAction.java new file mode 100644 index 000000000..3c3bd1418 --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeAction.java @@ -0,0 +1,293 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; + +import de.danoeh.antennapod.model.feed.FeedItem; + +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; + public static final Action DELETE = Action.DELETE; + + private final String podcast; + private final String episode; + private final String guid; + private final Action action; + private final Date timestamp; + private final int started; + private final int position; + private final int total; + + private EpisodeAction(Builder builder) { + this.podcast = builder.podcast; + this.episode = builder.episode; + this.guid = builder.guid; + this.action = builder.action; + this.timestamp = builder.timestamp; + this.started = builder.started; + this.position = builder.position; + this.total = builder.total; + } + + /** + * Create an episode action object from JSON representation. Mandatory fields are "podcast", + * "episode" and "action". + * + * @param object JSON representation + * @return episode action object, or null if mandatory values are missing + */ + public static EpisodeAction readFromJsonObject(JSONObject object) { + String podcast = object.optString("podcast", null); + String episode = object.optString("episode", null); + String actionString = object.optString("action", null); + if (TextUtils.isEmpty(podcast) || TextUtils.isEmpty(episode) || TextUtils.isEmpty(actionString)) { + return null; + } + EpisodeAction.Action action; + try { + 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)) { + 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(); + } + } + String guid = object.optString("guid", null); + if (!TextUtils.isEmpty(guid)) { + builder.guid(guid); + } + if (action == EpisodeAction.Action.PLAY) { + int started = object.optInt("started", -1); + int position = object.optInt("position", -1); + int total = object.optInt("total", -1); + if (started >= 0 && position > 0 && total > 0) { + builder + .started(started) + .position(position) + .total(total); + } + } + return builder.build(); + } + + public String getPodcast() { + return this.podcast; + } + + public String getEpisode() { + return this.episode; + } + + public String getGuid() { + return this.guid; + } + + public Action getAction() { + return this.action; + } + + private String getActionString() { + return this.action.name().toLowerCase(Locale.US); + } + + public Date getTimestamp() { + return this.timestamp; + } + + /** + * Returns the position (in seconds) at which the client started playback. + * + * @return start position (in seconds) + */ + public int getStarted() { + return this.started; + } + + /** + * Returns the position (in seconds) at which the client stopped playback. + * + * @return stop position (in seconds) + */ + public int getPosition() { + return this.position; + } + + /** + * Returns the total length of the file in seconds. + * + * @return total length in seconds + */ + public int getTotal() { + return this.total; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EpisodeAction)) { + return false; + } + + EpisodeAction that = (EpisodeAction) o; + return started == that.started + && position == that.position + && total == that.total + && action != that.action + && Objects.equals(podcast, that.podcast) + && Objects.equals(episode, that.episode) + && Objects.equals(timestamp, that.timestamp) + && Objects.equals(guid, that.guid); + } + + @Override + public int hashCode() { + int result = podcast != null ? podcast.hashCode() : 0; + result = 31 * result + (episode != null ? episode.hashCode() : 0); + result = 31 * result + (guid != null ? guid.hashCode() : 0); + result = 31 * result + (action != null ? action.hashCode() : 0); + result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0); + result = 31 * result + started; + result = 31 * result + position; + result = 31 * result + total; + return result; + } + + /** + * Returns a JSON object representation of this object. + * + * @return JSON object representation, or null if the object is invalid + */ + public JSONObject writeToJsonObject() { + JSONObject obj = new JSONObject(); + try { + obj.putOpt("podcast", this.podcast); + obj.putOpt("episode", this.episode); + obj.putOpt("guid", this.guid); + obj.put("action", this.getActionString()); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + obj.put("timestamp", formatter.format(this.timestamp)); + if (this.getAction() == Action.PLAY) { + obj.put("started", this.started); + obj.put("position", this.position); + obj.put("total", this.total); + } + } catch (JSONException e) { + Log.e(TAG, "writeToJSONObject(): " + e.getMessage()); + return null; + } + return obj; + } + + @NonNull + @Override + public String toString() { + return "EpisodeAction{" + + "podcast='" + podcast + '\'' + + ", episode='" + episode + '\'' + + ", guid='" + guid + '\'' + + ", action=" + action + + ", timestamp=" + timestamp + + ", started=" + started + + ", position=" + position + + ", total=" + total + + '}'; + } + + public enum Action { + NEW, DOWNLOAD, PLAY, DELETE + } + + public static class Builder { + + // mandatory + private final String podcast; + private final String episode; + private final Action action; + + // optional + private Date timestamp; + private int started = -1; + private int position = -1; + private int total = -1; + private String guid; + + public Builder(FeedItem item, Action action) { + this(item.getFeed().getDownloadUrl(), item.getMedia().getDownloadUrl(), action); + this.guid(item.getItemIdentifier()); + } + + public Builder(String podcast, String episode, Action action) { + this.podcast = podcast; + this.episode = episode; + this.action = action; + } + + public Builder timestamp(Date timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder guid(String guid) { + this.guid = guid; + return this; + } + + public Builder currentTimestamp() { + return timestamp(new Date()); + } + + public Builder started(int seconds) { + if (action == Action.PLAY) { + this.started = seconds; + } + return this; + } + + public Builder position(int seconds) { + if (action == Action.PLAY) { + this.position = seconds; + } + return this; + } + + public Builder total(int seconds) { + if (action == Action.PLAY) { + this.total = seconds; + } + return this; + } + + public EpisodeAction build() { + return new EpisodeAction(this); + } + + } + +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeActionChanges.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeActionChanges.java new file mode 100644 index 000000000..d2b17b492 --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/EpisodeActionChanges.java @@ -0,0 +1,34 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + + +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; + } + + @NonNull + @Override + public String toString() { + return "EpisodeActionGetResponse{" + + "episodeActions=" + episodeActions + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/ISyncService.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/ISyncService.java new file mode 100644 index 000000000..29632ed1e --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/ISyncService.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +import java.util.List; + +public interface ISyncService { + + void login() throws SyncServiceException; + + SubscriptionChanges getSubscriptionChanges(long lastSync) throws SyncServiceException; + + UploadChangesResponse uploadSubscriptionChanges( + List<String> addedFeeds, List<String> removedFeeds) throws SyncServiceException; + + EpisodeActionChanges getEpisodeActionChanges(long lastSync) throws SyncServiceException; + + UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> queuedEpisodeActions) + throws SyncServiceException; + + void logout() throws SyncServiceException; +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SubscriptionChanges.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SubscriptionChanges.java new file mode 100644 index 000000000..c0c9f131d --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SubscriptionChanges.java @@ -0,0 +1,39 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +import androidx.annotation.NonNull; + +import java.util.List; + +public class SubscriptionChanges { + private final List<String> added; + private final List<String> removed; + private final long timestamp; + + public SubscriptionChanges(@NonNull List<String> added, + @NonNull List<String> removed, + long timestamp) { + this.added = added; + this.removed = removed; + this.timestamp = timestamp; + } + + @Override + public String toString() { + return "SubscriptionChange [added=" + added.toString() + + ", removed=" + removed.toString() + ", timestamp=" + + timestamp + "]"; + } + + public List<String> getAdded() { + return added; + } + + public List<String> getRemoved() { + return removed; + } + + public long getTimestamp() { + return timestamp; + } + +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SyncServiceException.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SyncServiceException.java new file mode 100644 index 000000000..5ccedd785 --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SyncServiceException.java @@ -0,0 +1,13 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +public class SyncServiceException extends Exception { + private static final long serialVersionUID = 1L; + + public SyncServiceException(String message) { + super(message); + } + + public SyncServiceException(Throwable cause) { + super(cause); + } +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProvider.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProvider.java new file mode 100644 index 000000000..8c4047b6c --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProvider.java @@ -0,0 +1,25 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +public enum SynchronizationProvider { + GPODDER_NET("GPODDER_NET"), + NEXTCLOUD_GPODDER("NEXTCLOUD_GPODDER"); + + public static SynchronizationProvider fromIdentifier(String provider) { + for (SynchronizationProvider synchronizationProvider : SynchronizationProvider.values()) { + if (synchronizationProvider.getIdentifier().equals(provider)) { + return synchronizationProvider; + } + } + return null; + } + + private final String identifier; + + SynchronizationProvider(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } +} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProviderViewData.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProviderViewData.java deleted file mode 100644 index 19624a95a..000000000 --- a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationProviderViewData.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.danoeh.antennapod.net.sync.serviceinterface; - -public enum SynchronizationProviderViewData { - GPODDER_NET( - "GPODDER_NET", - R.string.gpodnet_description, - R.drawable.gpodder_icon - ), - NEXTCLOUD_GPODDER( - "NEXTCLOUD_GPODDER", - R.string.synchronization_summary_nextcloud, - R.drawable.nextcloud_logo - ); - - public static SynchronizationProviderViewData fromIdentifier(String provider) { - for (SynchronizationProviderViewData synchronizationProvider : SynchronizationProviderViewData.values()) { - if (synchronizationProvider.getIdentifier().equals(provider)) { - return synchronizationProvider; - } - } - return null; - } - - private final String identifier; - private final int iconResource; - private final int summaryResource; - - SynchronizationProviderViewData(String identifier, int summaryResource, int iconResource) { - this.identifier = identifier; - this.iconResource = iconResource; - this.summaryResource = summaryResource; - } - - public String getIdentifier() { - return identifier; - } - - public int getIconResource() { - return iconResource; - } - - public int getSummaryResource() { - return summaryResource; - } -} diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java index 8c94c44e7..ad235130a 100644 --- a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java @@ -4,7 +4,6 @@ import android.content.Context; import de.danoeh.antennapod.storage.preferences.SynchronizationSettings; import de.danoeh.antennapod.model.feed.FeedMedia; -import de.danoeh.antennapod.net.sync.model.EpisodeAction; public class SynchronizationQueueSink { // To avoid a dependency loop of every class to SyncService, and from SyncService back to every class. diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueStorage.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueStorage.java index 0ae794ac8..55dc07ae8 100644 --- a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueStorage.java +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueStorage.java @@ -9,7 +9,6 @@ import org.json.JSONException; import java.util.ArrayList; import de.danoeh.antennapod.storage.preferences.SynchronizationSettings; -import de.danoeh.antennapod.net.sync.model.EpisodeAction; public class SynchronizationQueueStorage { diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/UploadChangesResponse.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/UploadChangesResponse.java new file mode 100644 index 000000000..64bddc260 --- /dev/null +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/UploadChangesResponse.java @@ -0,0 +1,13 @@ +package de.danoeh.antennapod.net.sync.serviceinterface; + +public abstract class UploadChangesResponse { + + /** + * timestamp/ID that can be used for requesting changes since this upload. + */ + public final long timestamp; + + public UploadChangesResponse(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/net/sync/service-interface/src/main/res/drawable-nodpi/gpodder_icon.png b/net/sync/service-interface/src/main/res/drawable-nodpi/gpodder_icon.png Binary files differdeleted file mode 100644 index cd133aa98..000000000 --- a/net/sync/service-interface/src/main/res/drawable-nodpi/gpodder_icon.png +++ /dev/null diff --git a/net/sync/service-interface/src/main/res/drawable-nodpi/nextcloud_logo.png b/net/sync/service-interface/src/main/res/drawable-nodpi/nextcloud_logo.png Binary files differdeleted file mode 100644 index 2164e37fb..000000000 --- a/net/sync/service-interface/src/main/res/drawable-nodpi/nextcloud_logo.png +++ /dev/null diff --git a/net/sync/service-interface/src/main/res/values/ids.xml b/net/sync/service-interface/src/main/res/values/ids.xml deleted file mode 100644 index 842e421ea..000000000 --- a/net/sync/service-interface/src/main/res/values/ids.xml +++ /dev/null @@ -1,5 +0,0 @@ -<resources> - <item name="notification_gpodnet_sync_error" type="id"/> - <item name="notification_gpodnet_sync_autherror" type="id"/> - <item name="pending_intent_sync_error" type="id"/> -</resources> |