package com.battlelancer.seriesguide.util; import android.content.ContentValues; import android.content.Context; import android.os.AsyncTask; import android.text.format.DateUtils; import com.battlelancer.seriesguide.SgApp; import com.battlelancer.seriesguide.provider.SeriesGuideContract; import com.battlelancer.seriesguide.traktapi.SgTrakt; import com.uwetrottmann.androidutils.AndroidUtils; import com.uwetrottmann.trakt5.entities.Ratings; import com.uwetrottmann.trakt5.services.Episodes; import com.uwetrottmann.trakt5.services.Shows; import dagger.Lazy; import javax.inject.Inject; import timber.log.Timber; public class TraktRatingsTask extends AsyncTask<Void, Void, Void> { private static final int HARD_CACHE_CAPACITY = 50; private static final long MAXIMUM_AGE = 5 * DateUtils.MINUTE_IN_MILLIS; // Hard cache, with a fixed maximum capacity private final static android.support.v4.util.LruCache<Long, Long> sCache = new android.support.v4.util.LruCache<>(HARD_CACHE_CAPACITY); private final Context context; private final int showTvdbId; private final int episodeTvdbId; private final int season; private final int episode; @Inject Lazy<Shows> traktShows; @Inject Lazy<Episodes> traktEpisodes; /** * Loads the latest ratings for the given show from trakt and saves them to the database. If * ratings were loaded recently, might do nothing. */ public TraktRatingsTask(SgApp app, int showTvdbId) { this(app, showTvdbId, 0, 0, 0); } /** * Loads the latest ratings for the given episode from trakt and saves them to the database. If * ratings were loaded recently, might do nothing. */ public TraktRatingsTask(SgApp app, int showTvdbId, int episodeTvdbId, int season, int episode) { this.context = app; app.getServicesComponent().inject(this); this.showTvdbId = showTvdbId; this.episodeTvdbId = episodeTvdbId; this.season = season; this.episode = episode; } @Override protected Void doInBackground(Void... params) { long ratingId = createUniqueId(showTvdbId, episodeTvdbId); // avoid saving ratings too frequently // (network requests are cached, but also avoiding database writes) long currentTimeMillis = System.currentTimeMillis(); synchronized (sCache) { Long lastUpdateMillis = sCache.get(ratingId); // if the ratings were just updated, do nothing if (lastUpdateMillis != null && lastUpdateMillis > currentTimeMillis - MAXIMUM_AGE) { Timber.d("Just loaded rating for %s, skip.", ratingId); return null; } } if (isCancelled() || !AndroidUtils.isNetworkConnected(context)) { return null; } // look up show trakt id Integer showTraktId = ShowTools.getShowTraktId(context, showTvdbId); if (showTraktId == null) { Timber.d("Show %s has no trakt id, skip.", showTvdbId); return null; } String showTraktIdString = String.valueOf(showTraktId); boolean isShowNotEpisode = episodeTvdbId == 0; Ratings ratings; if (isShowNotEpisode) { ratings = SgTrakt.executeCall(context, traktShows.get().ratings(showTraktIdString), "get show rating"); } else { ratings = SgTrakt.executeCall(context, traktEpisodes.get().ratings(showTraktIdString, season, episode), "get episode rating"); } if (ratings != null && ratings.rating != null && ratings.votes != null) { if (isShowNotEpisode) { saveShowRating(ratings); } else { saveEpisodeRating(ratings); } } // cache download time to avoid saving ratings too frequently synchronized (sCache) { sCache.put(ratingId, currentTimeMillis); } return null; } private void saveEpisodeRating(Ratings ratings) { ContentValues values = new ContentValues(); values.put(SeriesGuideContract.Episodes.RATING_GLOBAL, ratings.rating); values.put(SeriesGuideContract.Episodes.RATING_VOTES, ratings.votes); context.getContentResolver() .update(SeriesGuideContract.Episodes.buildEpisodeUri(episodeTvdbId), values, null, null); // notify withshow uri as well (used by episode details view) context.getContentResolver() .notifyChange(SeriesGuideContract.Episodes.buildEpisodeWithShowUri(episodeTvdbId), null); } private void saveShowRating(Ratings ratings) { ContentValues values = new ContentValues(); values.put(SeriesGuideContract.Shows.RATING_GLOBAL, ratings.rating); values.put(SeriesGuideContract.Shows.RATING_VOTES, ratings.votes); context.getContentResolver() .update(SeriesGuideContract.Shows.buildShowUri(showTvdbId), values, null, null); } /** * Creates a unique id using the <a href="https://en.wikipedia.org/wiki/Cantor_pairing_function">Cantor * pairing</a> function. */ private long createUniqueId(int showTvdbId, int episodeTvdbId) { return ((showTvdbId + episodeTvdbId) * (showTvdbId + episodeTvdbId + 1) / 2) + episodeTvdbId; } }