package com.florianmski.tracktoid.trakt; import android.content.ContentValues; import android.content.Context; import com.florianmski.tracktoid.utils.CVHelper; import com.florianmski.tracktoid.utils.DateHelper; import com.florianmski.tracktoid.utils.DbHelper; import com.florianmski.tracktoid.TraktoidPrefs; import com.florianmski.tracktoid.data.database.ProviderSchematic; import com.florianmski.tracktoid.data.database.columns.EpisodeColumns; import com.florianmski.tracktoid.data.database.columns.SyncColumns; import com.florianmski.tracktoid.rx.observables.TraktObservable; import com.uwetrottmann.trakt.v2.entities.Episode; import com.uwetrottmann.trakt.v2.entities.EpisodeIds; import com.uwetrottmann.trakt.v2.entities.Movie; import com.uwetrottmann.trakt.v2.entities.MovieIds; import com.uwetrottmann.trakt.v2.entities.Show; import com.uwetrottmann.trakt.v2.entities.ShowIds; import com.uwetrottmann.trakt.v2.entities.SyncEpisode; import com.uwetrottmann.trakt.v2.entities.SyncErrors; import com.uwetrottmann.trakt.v2.entities.SyncItems; import com.uwetrottmann.trakt.v2.entities.SyncMovie; import com.uwetrottmann.trakt.v2.entities.SyncResponse; import com.uwetrottmann.trakt.v2.entities.SyncSeason; import com.uwetrottmann.trakt.v2.entities.SyncShow; import com.uwetrottmann.trakt.v2.entities.SyncStats; import com.uwetrottmann.trakt.v2.enums.Rating; import com.uwetrottmann.trakt.v2.exceptions.OAuthUnauthorizedException; import org.joda.time.DateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import rx.Observable; import rx.functions.Action0; import rx.functions.Action1; public class TraktSender { // TODO should support seasons as well private SyncItems2 syncItems = new SyncItems2(); private List<SyncShow> syncShows; private List<SyncSeason> syncSeasons; private List<SyncEpisode> syncEpisodes; private List<SyncMovie> syncMovies; private Context context; private TraktSender(Context context, BaseBuilder builder) { this.context = context.getApplicationContext(); this.syncShows = builder.syncShows; this.syncSeasons = builder.syncSeasons; this.syncEpisodes = builder.syncEpisodes; this.syncMovies = builder.syncMovies; } public Observable<SyncResponse> history(final boolean add) { return getObservable(new TraktSenderObservable(add) { @Override protected SyncResponse addItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().addItemsToWatchedHistory(syncItems); } @Override protected SyncResponse deleteItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().deleteItemsFromWatchedHistory(syncItems); } }, new Action0() { @Override public void call() { ContentValues contentValues = new CVHelper() .put(SyncColumns.WATCHED, add) .put(SyncColumns.LAST_WATCHED_AT, DateHelper.now()) .put(SyncColumns.WATCHLISTED, false) .putNull(SyncColumns.WATCHLISTED_AT) .get(); DbHelper.updateMovies(context, contentValues, syncMovies); // if seen change, all the episodes change too for (SyncShow syncShow : syncShows) updateEpisodesTillNow(syncShow.ids.trakt, contentValues, add); DbHelper.updateEpisodes(context, contentValues, syncEpisodes); } }); } public Observable<SyncResponse> collection(final boolean add) { return getObservable(new TraktSenderObservable(add) { @Override protected SyncResponse addItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().addItemsToCollection(syncItems); } @Override protected SyncResponse deleteItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().deleteItemsFromCollection(syncItems); } }, new Action0() { @Override public void call() { ContentValues contentValues = new CVHelper() .put(SyncColumns.COLLECTED, add) .put(SyncColumns.COLLECTED_AT, DateHelper.now()) .get(); DbHelper.updateMovies(context, contentValues, syncMovies); // if collection change, all the episodes change too for (SyncShow syncShow : syncShows) updateEpisodesTillNow(syncShow.ids.trakt, contentValues, add); DbHelper.updateEpisodes(context, contentValues, syncEpisodes); } }); } private void updateEpisodesTillNow(int traktShow, ContentValues contentValues, boolean tillNow) { // if we add a show, we want to collect/watch until today's date (and not include the specials) // if we remove a show, we want to remove everything even if user as marked sthg in the future if(tillNow) { DbHelper.update(context, ProviderSchematic.Episodes.fromShow(String.valueOf(traktShow)), contentValues, EpisodeColumns.FIRST_AIRED + "<=? AND " + EpisodeColumns.SEASON + "!=?", String.valueOf(System.currentTimeMillis()), String.valueOf(0)); } else { DbHelper.update(context, ProviderSchematic.Episodes.fromShow(String.valueOf(traktShow)), contentValues); } } public Observable<SyncResponse> watchlist(final boolean add) { return getObservable(new TraktSenderObservable(add) { @Override protected SyncResponse addItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().addItemsToWatchlist(syncItems); } @Override protected SyncResponse deleteItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().deleteItemsFromWatchlist(syncItems); } }, new Action0() { @Override public void call() { ContentValues contentValues = new CVHelper() .put(SyncColumns.WATCHLISTED, add) .put(SyncColumns.WATCHLISTED_AT, DateHelper.now()) .get(); DbHelper.updateMovies(context, contentValues, syncMovies); DbHelper.updateShows(context, contentValues, syncShows); DbHelper.updateEpisodes(context, contentValues, syncEpisodes); } }); } public Observable<SyncResponse> rating(final boolean rate) { return getObservable(new TraktSenderObservable(rate) { @Override protected SyncResponse addItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().addRatings(syncItems); } @Override protected SyncResponse deleteItems() throws OAuthUnauthorizedException { return TraktManager.getInstance().sync().deleteRatings(syncItems); } }, new Action0() { @Override public void call() { for(SyncMovie syncMovie : syncMovies) DbHelper.updateMovie(context, getContentValues(syncMovie.rating, syncMovie.rated_at), String.valueOf(syncMovie.ids.trakt)); for(SyncShow syncShow : syncShows) DbHelper.updateShow(context, getContentValues(syncShow.rating, syncShow.rated_at), String.valueOf(syncShow.ids.trakt)); for(SyncEpisode syncEpisode : syncEpisodes) DbHelper.updateEpisode(context, getContentValues(syncEpisode.rating, syncEpisode.rated_at), String.valueOf(syncEpisode.ids.trakt)); } private ContentValues getContentValues(Rating rating, DateTime ratedAt) { CVHelper cvHelper = new CVHelper().put(SyncColumns.RATED_AT, ratedAt); if(rating == null) cvHelper.putNull(SyncColumns.RATING); else cvHelper.put(SyncColumns.RATING, rating.value); return cvHelper.get(); } }); } private Observable<SyncResponse> getObservable(TraktSenderObservable observable, Action0 updateDBAction) { // if user is logged in, send info to trakt if(TraktoidPrefs.INSTANCE.isUserLoggedIn()) { return Observable .create(observable) .doOnNext(new Action1<SyncResponse>() { @Override public void call(SyncResponse syncResponse) { SyncStats syncStatsAdded = syncResponse.added; SyncStats syncStatsDeleted = syncResponse.deleted; SyncStats syncStatsExisting = syncResponse.existing; SyncErrors syncStatsNotFound = syncResponse.not_found; // in case an item hasn't been found, remove it from the list where it belongs so we don't update // the field later if (syncStatsNotFound != null) { for (SyncShow syncShowNotFound : syncStatsNotFound.shows) { for (Iterator<SyncShow> iterator = syncShows.iterator(); iterator.hasNext(); ) { SyncShow syncShow = iterator.next(); if (syncShow.ids.trakt.equals(syncShowNotFound.ids.trakt)) iterator.remove(); } } for (SyncEpisode syncEpisodeNotFound : syncStatsNotFound.episodes) { for (Iterator<SyncEpisode> iterator = syncEpisodes.iterator(); iterator.hasNext(); ) { SyncEpisode syncEpisode = iterator.next(); if (syncEpisode.ids.trakt.equals(syncEpisodeNotFound.ids.trakt)) iterator.remove(); } } for (SyncMovie syncMovieNotFound : syncStatsNotFound.movies) { for (Iterator<SyncMovie> iterator = syncMovies.iterator(); iterator.hasNext(); ) { SyncMovie syncMovie = iterator.next(); if (syncMovie.ids.trakt.equals(syncMovieNotFound.ids.trakt)) iterator.remove(); } } } } }).doOnCompleted(updateDBAction); } // if not do it just locally else { SyncResponse syncResponse = new SyncResponse(); SyncStats syncStats = new SyncStats(); syncStats.shows = syncShows.size(); syncStats.seasons = syncSeasons.size(); syncStats.episodes = syncEpisodes.size(); syncStats.movies = syncMovies.size(); if(observable.add) syncResponse.added = syncStats; else syncResponse.deleted = syncStats; return Observable.just(syncResponse).doOnCompleted(updateDBAction); } } private abstract static class BaseBuilder { protected Context context; protected List<SyncShow> syncShows = new ArrayList<>(); protected List<SyncSeason> syncSeasons = new ArrayList<>(); protected List<SyncEpisode> syncEpisodes = new ArrayList<>(); protected List<SyncMovie> syncMovies = new ArrayList<>(); public abstract Observable<SyncResponse> getObservable(boolean add); public BaseBuilder(Context context) { this.context = context; } protected SyncMovie getSyncMovie(MovieIds ids, DateTime dateTime) { return new SyncMovie().id(ids); } protected SyncShow getSyncShow(ShowIds ids, DateTime dateTime) { return new SyncShow().id(ids); } protected SyncEpisode getSyncEpisode(EpisodeIds ids, DateTime dateTime) { return new SyncEpisode().id(ids); } public void clear() { syncShows.clear(); syncMovies.clear(); syncEpisodes.clear(); } protected TraktSender build() { return new TraktSender(context, this); } } public static abstract class Builder<T extends Builder<T>> extends BaseBuilder { protected abstract T self(); public Builder(Context context) { super(context); } public T movie(Movie movie, DateTime dateTime) { syncMovies.add(getSyncMovie(movie.ids, dateTime)); return self(); } public T movie(Movie movie) { movie(movie, DateHelper.now()); return self(); } public T movies(List<Movie> movies, DateTime dateTime) { for(Movie movie : movies) syncMovies.add(getSyncMovie(movie.ids, dateTime)); return self(); } public T movies(List<Movie> movies) { movies(movies, DateHelper.now()); return self(); } public T show(Show show, DateTime dateTime) { syncShows.add(getSyncShow(show.ids, dateTime)); return self(); } public T show(Show show) { show(show, DateHelper.now()); return self(); } public T shows(List<Show> shows, DateTime dateTime) { for(Show show : shows) syncShows.add(getSyncShow(show.ids, dateTime)); return self(); } public T shows(List<Show> shows) { shows(shows, DateHelper.now()); return self(); } public T episode(Episode episode, DateTime dateTime) { syncEpisodes.add(getSyncEpisode(episode.ids, dateTime)); return self(); } public T episode(Episode episode) { episode(episode, DateHelper.now()); return self(); } public T episodes(List<Episode> episodes, DateTime dateTime) { for(Episode episode : episodes) syncEpisodes.add(getSyncEpisode(episode.ids, dateTime)); return self(); } public T episodes(List<Episode> episodes) { episodes(episodes, DateHelper.now()); return self(); } } public static class HistoryBuilder extends Builder<HistoryBuilder> { public HistoryBuilder(Context context) { super(context); } @Override protected HistoryBuilder self() { return this; } @Override protected SyncMovie getSyncMovie(MovieIds ids, DateTime dateTime) { return super.getSyncMovie(ids, dateTime).watchedAt(dateTime); } @Override protected SyncShow getSyncShow(ShowIds ids, DateTime dateTime) { return super.getSyncShow(ids, dateTime).watchedAt(dateTime); } @Override protected SyncEpisode getSyncEpisode(EpisodeIds ids, DateTime dateTime) { return super.getSyncEpisode(ids, dateTime).watchedAt(dateTime); } @Override public Observable<SyncResponse> getObservable(boolean add) { return build().history(add); } } public static class CollectionBuilder extends Builder<CollectionBuilder> { public CollectionBuilder(Context context) { super(context); } @Override protected CollectionBuilder self() { return this; } @Override protected SyncMovie getSyncMovie(MovieIds ids, DateTime dateTime) { return super.getSyncMovie(ids, dateTime).collectedAt(dateTime); } @Override protected SyncShow getSyncShow(ShowIds ids, DateTime dateTime) { return super.getSyncShow(ids, dateTime).collectedAt(dateTime); } @Override protected SyncEpisode getSyncEpisode(EpisodeIds ids, DateTime dateTime) { return super.getSyncEpisode(ids, dateTime).collectedAt(dateTime); } @Override public Observable<SyncResponse> getObservable(boolean add) { return build().collection(add); } } public static class WatchlistBuilder extends Builder<WatchlistBuilder> { public WatchlistBuilder(Context context) { super(context); } @Override protected WatchlistBuilder self() { return this; } @Override public Observable<SyncResponse> getObservable(boolean add) { return build().watchlist(add); } } public static class RatingBuilder extends BaseBuilder { public RatingBuilder(Context context) { super(context); } @Override protected SyncMovie getSyncMovie(MovieIds ids, DateTime dateTime) { return super.getSyncMovie(ids, dateTime).ratedAt(dateTime); } @Override protected SyncShow getSyncShow(ShowIds ids, DateTime dateTime) { return super.getSyncShow(ids, dateTime).ratedAt(dateTime); } @Override protected SyncEpisode getSyncEpisode(EpisodeIds ids, DateTime dateTime) { return super.getSyncEpisode(ids, dateTime).ratedAt(dateTime); } public RatingBuilder movie(Movie movie, Rating rating, DateTime dateTime) { syncMovies.add(getSyncMovie(movie.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder movie(Movie movie, Rating rating) { movie(movie, rating, DateHelper.now()); return this; } public RatingBuilder movies(List<Movie> movies, Rating rating, DateTime dateTime) { for(Movie movie : movies) syncMovies.add(getSyncMovie(movie.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder movies(List<Movie> movies, Rating rating) { movies(movies, rating, DateHelper.now()); return this; } public RatingBuilder show(Show show, Rating rating, DateTime dateTime) { syncShows.add(getSyncShow(show.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder show(Show show, Rating rating) { show(show, rating, DateHelper.now()); return this; } public RatingBuilder shows(List<Show> shows, Rating rating, DateTime dateTime) { for(Show show : shows) syncShows.add(getSyncShow(show.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder shows(List<Show> shows, Rating rating) { shows(shows, rating, DateHelper.now()); return this; } public RatingBuilder episode(Episode episode, Rating rating, DateTime dateTime) { syncEpisodes.add(getSyncEpisode(episode.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder episode(Episode episode, Rating rating) { episode(episode, rating, DateHelper.now()); return this; } public RatingBuilder episodes(List<Episode> episodes, Rating rating, DateTime dateTime) { for(Episode episode : episodes) syncEpisodes.add(getSyncEpisode(episode.ids, dateTime).ratedAt(dateTime).rating(rating)); return this; } public RatingBuilder episodes(List<Episode> episodes, Rating rating) { episodes(episodes, rating, DateHelper.now()); return this; } @Override public Observable<SyncResponse> getObservable(boolean add) { return build().rating(add); } } private abstract class TraktSenderObservable extends TraktObservable<SyncResponse> { public boolean add; protected abstract SyncResponse addItems() throws OAuthUnauthorizedException; protected abstract SyncResponse deleteItems() throws OAuthUnauthorizedException; public TraktSenderObservable(boolean add) { this.add = add; } @Override public SyncResponse fire() throws OAuthUnauthorizedException { if(!syncShows.isEmpty()) syncItems.shows(syncShows); if(!syncSeasons.isEmpty()) syncItems.seasons(syncSeasons); if(!syncEpisodes.isEmpty()) syncItems.episodes(syncEpisodes); if(!syncMovies.isEmpty()) syncItems.movies(syncMovies); return add ? addItems() : deleteItems(); } } private class SyncItems2 extends SyncItems { public List<SyncSeason> seasons; public List<SyncEpisode> episodes; public SyncItems seasons(SyncSeason season) { LinkedList<SyncSeason> list = new LinkedList<>(); list.add(season); return seasons(list); } public SyncItems seasons(List<SyncSeason> seasons) { this.seasons = seasons; return this; } public SyncItems episodes(SyncEpisode episode) { LinkedList<SyncEpisode> list = new LinkedList<>(); list.add(episode); return episodes(list); } public SyncItems episodes(List<SyncEpisode> episodes) { this.episodes = episodes; return this; } } }