diff options
Diffstat (limited to 'net/sync/model/src')
7 files changed, 387 insertions, 0 deletions
diff --git a/net/sync/model/src/main/AndroidManifest.xml b/net/sync/model/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a7bd1e34b --- /dev/null +++ b/net/sync/model/src/main/AndroidManifest.xml @@ -0,0 +1 @@ +<manifest package="de.danoeh.antennapod.net.sync.model" /> diff --git a/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeAction.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeAction.java new file mode 100644 index 000000000..1aae5c811 --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeAction.java @@ -0,0 +1,267 @@ +package de.danoeh.antennapod.net.sync.model; + +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.util.ObjectsCompat; +import de.danoeh.antennapod.model.feed.FeedItem; +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.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; + public static final Action DELETE = Action.DELETE; + + private final String podcast; + private final String episode; + 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.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(); + } + } + 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 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 + && ObjectsCompat.equals(podcast, that.podcast) + && ObjectsCompat.equals(episode, that.episode) + && ObjectsCompat.equals(timestamp, that.timestamp); + } + + @Override + public int hashCode() { + int result = podcast != null ? podcast.hashCode() : 0; + result = 31 * result + (episode != null ? episode.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.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 + '\'' + + ", 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; + + public Builder(FeedItem item, Action action) { + this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action); + } + + 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 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/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeActionChanges.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeActionChanges.java new file mode 100644 index 000000000..570e012c5 --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/EpisodeActionChanges.java @@ -0,0 +1,34 @@ +package de.danoeh.antennapod.net.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; + } + + @NonNull + @Override + public String toString() { + return "EpisodeActionGetResponse{" + + "episodeActions=" + episodeActions + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/ISyncService.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/ISyncService.java new file mode 100644 index 000000000..9c75e5dac --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/ISyncService.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.net.sync.model; + +import java.util.List; + +public interface ISyncService { + + void login() throws SyncServiceException; + + SubscriptionChanges getSubscriptionChanges(long lastSync) throws SyncServiceException; + + UploadChangesResponse uploadSubscriptionChanges( + List<String> addedFeeds, List<String> removedFeeds) throws SyncServiceException; + + EpisodeActionChanges getEpisodeActionChanges(long lastSync) throws SyncServiceException; + + UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> queuedEpisodeActions) + throws SyncServiceException; + + void logout() throws SyncServiceException; +} diff --git a/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/SubscriptionChanges.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/SubscriptionChanges.java new file mode 100644 index 000000000..2fbc8b45e --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/SubscriptionChanges.java @@ -0,0 +1,39 @@ +package de.danoeh.antennapod.net.sync.model; + +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/model/src/main/java/de/danoeh/antennapod/net/sync/model/SyncServiceException.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/SyncServiceException.java new file mode 100644 index 000000000..57262db17 --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/SyncServiceException.java @@ -0,0 +1,13 @@ +package de.danoeh.antennapod.net.sync.model; + +public class SyncServiceException extends Exception { + private static final long serialVersionUID = 1L; + + public SyncServiceException(String message) { + super(message); + } + + public SyncServiceException(Throwable cause) { + super(cause); + } +} diff --git a/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/UploadChangesResponse.java b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/UploadChangesResponse.java new file mode 100644 index 000000000..7503f429b --- /dev/null +++ b/net/sync/model/src/main/java/de/danoeh/antennapod/net/sync/model/UploadChangesResponse.java @@ -0,0 +1,13 @@ +package de.danoeh.antennapod.net.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; + } +} |