Search in sources :

Example 1 with Sync

use of com.uwetrottmann.trakt5.services.Sync in project SeriesGuide by UweTrottmann.

the class TraktTools method downloadShowRatings.

/**
     * Downloads trakt show ratings and applies the latest ones to the database.
     *
     * <p> To apply all ratings, set {@link TraktSettings#KEY_LAST_SHOWS_RATED_AT} to 0.
     */
public UpdateResult downloadShowRatings(@Nullable DateTime ratedAt) {
    if (ratedAt == null) {
        Timber.e("downloadShowRatings: null rated_at");
        return UpdateResult.INCOMPLETE;
    }
    long lastRatedAt = TraktSettings.getLastShowsRatedAt(context);
    if (!ratedAt.isAfter(lastRatedAt)) {
        // not initial sync, no ratings have changed
        Timber.d("downloadShowRatings: no changes since %tF %tT", lastRatedAt, lastRatedAt);
        return UpdateResult.SUCCESS;
    }
    if (!TraktCredentials.get(context).hasCredentials()) {
        return UpdateResult.INCOMPLETE;
    }
    // download rated shows
    List<RatedShow> ratedShows;
    try {
        Response<List<RatedShow>> response = traktSync.get().ratingsShows(RatingsFilter.ALL, Extended.DEFAULT_MIN).execute();
        if (response.isSuccessful()) {
            ratedShows = response.body();
        } else {
            if (SgTrakt.isUnauthorized(context, response)) {
                return UpdateResult.INCOMPLETE;
            }
            SgTrakt.trackFailedRequest(context, "get show ratings", response);
            return UpdateResult.INCOMPLETE;
        }
    } catch (IOException e) {
        SgTrakt.trackFailedRequest(context, "get show ratings", e);
        return UpdateResult.INCOMPLETE;
    }
    if (ratedShows == null) {
        Timber.e("downloadShowRatings: null response");
        return UpdateResult.INCOMPLETE;
    }
    if (ratedShows.isEmpty()) {
        Timber.d("downloadShowRatings: no ratings on trakt");
        return UpdateResult.SUCCESS;
    }
    // trakt last activity rated_at timestamp is set after the rating timestamp
    // so include ratings that are a little older
    long ratedAtThreshold = lastRatedAt - 5 * DateUtils.MINUTE_IN_MILLIS;
    // go through ratings, latest first (trakt sends in that order)
    ArrayList<ContentProviderOperation> batch = new ArrayList<>();
    for (RatedShow show : ratedShows) {
        if (show.rating == null || show.show == null || show.show.ids == null || show.show.ids.tvdb == null) {
            // skip, can't handle
            continue;
        }
        if (show.rated_at != null && show.rated_at.isBefore(ratedAtThreshold)) {
            // no need to apply older ratings again
            break;
        }
        // if a show does not exist, this update will do nothing
        ContentProviderOperation op = ContentProviderOperation.newUpdate(SeriesGuideContract.Shows.buildShowUri(show.show.ids.tvdb)).withValue(SeriesGuideContract.Shows.RATING_USER, show.rating.value).build();
        batch.add(op);
    }
    // apply database updates
    try {
        DBUtils.applyInSmallBatches(context, batch);
    } catch (OperationApplicationException e) {
        Timber.e(e, "downloadShowRatings: database update failed");
        return UpdateResult.INCOMPLETE;
    }
    // save last rated instant
    PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(TraktSettings.KEY_LAST_SHOWS_RATED_AT, ratedAt.getMillis()).commit();
    Timber.d("downloadShowRatings: success, last rated_at %tF %tT", ratedAt.getMillis(), ratedAt.getMillis());
    return UpdateResult.SUCCESS;
}
Also used : ContentProviderOperation(android.content.ContentProviderOperation) RatedShow(com.uwetrottmann.trakt5.entities.RatedShow) ArrayList(java.util.ArrayList) List(java.util.List) ArrayList(java.util.ArrayList) LinkedList(java.util.LinkedList) IOException(java.io.IOException) OperationApplicationException(android.content.OperationApplicationException)

Example 2 with Sync

use of com.uwetrottmann.trakt5.services.Sync in project SeriesGuide by UweTrottmann.

the class TraktTools method processTraktSeasons.

/**
     * Sync the watched/collected episodes of the given trakt show with the local episodes. The
     * given show has to be watched/collected on trakt.
     *
     * @param isInitialSync If {@code true}, will upload watched/collected episodes that are not
     * watched/collected on trakt. If {@code false}, will set them not watched/collected (if not
     * skipped) to mirror the trakt episode.
     */
public int processTraktSeasons(boolean isInitialSync, int localShow, @NonNull BaseShow traktShow, @NonNull Flag flag) {
    HashMap<Integer, BaseSeason> traktSeasons = buildTraktSeasonsMap(traktShow.seasons);
    Cursor localSeasonsQuery = context.getContentResolver().query(SeriesGuideContract.Seasons.buildSeasonsOfShowUri(localShow), new String[] { SeriesGuideContract.Seasons._ID, SeriesGuideContract.Seasons.COMBINED }, null, null, null);
    if (localSeasonsQuery == null) {
        return FAILED;
    }
    final ArrayList<ContentProviderOperation> batch = new ArrayList<>();
    List<SyncSeason> syncSeasons = new ArrayList<>();
    while (localSeasonsQuery.moveToNext()) {
        String seasonId = localSeasonsQuery.getString(0);
        int seasonNumber = localSeasonsQuery.getInt(1);
        if (traktSeasons.containsKey(seasonNumber)) {
            // season watched/collected on trakt
            if (!processTraktEpisodes(isInitialSync, seasonId, traktSeasons.get(seasonNumber), syncSeasons, flag)) {
                return FAILED;
            }
        } else {
            // season not watched/collected on trakt
            if (isInitialSync) {
                // schedule all watched/collected episodes of this season for upload
                SyncSeason syncSeason = buildSyncSeason(seasonId, seasonNumber, flag);
                if (syncSeason != null) {
                    syncSeasons.add(syncSeason);
                }
            } else {
                // set all watched/collected episodes of season not watched/collected
                batch.add(ContentProviderOperation.newUpdate(SeriesGuideContract.Episodes.buildEpisodesOfSeasonUri(seasonId)).withSelection(flag.clearFlagSelection, null).withValue(flag.databaseColumn, flag.notFlaggedValue).build());
            }
        }
    }
    localSeasonsQuery.close();
    try {
        DBUtils.applyInSmallBatches(context, batch);
    } catch (OperationApplicationException e) {
        Timber.e(e, "Setting seasons unwatched failed.");
    }
    if (syncSeasons.size() > 0) {
        // upload watched/collected episodes for this show
        Integer showTraktId = ShowTools.getShowTraktId(context, localShow);
        if (showTraktId == null) {
            // show should have a trakt id, give up
            return FAILED;
        }
        return uploadEpisodes(showTraktId, syncSeasons, flag);
    } else {
        return SUCCESS;
    }
}
Also used : ContentProviderOperation(android.content.ContentProviderOperation) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) BaseSeason(com.uwetrottmann.trakt5.entities.BaseSeason) OperationApplicationException(android.content.OperationApplicationException) SyncSeason(com.uwetrottmann.trakt5.entities.SyncSeason)

Example 3 with Sync

use of com.uwetrottmann.trakt5.services.Sync in project SeriesGuide by UweTrottmann.

the class TraktTools method downloadWatchedMovies.

/**
     * Downloads trakt movie watched flags and mirrors them in the local database. Does NOT upload
     * any flags (e.g. trakt is considered the truth).
     */
public UpdateResult downloadWatchedMovies(DateTime watchedAt) {
    if (watchedAt == null) {
        Timber.e("downloadWatchedMovies: null watched_at");
        return UpdateResult.INCOMPLETE;
    }
    long lastWatchedAt = TraktSettings.getLastMoviesWatchedAt(context);
    if (!watchedAt.isAfter(lastWatchedAt)) {
        // not initial sync, no watched flags have changed
        Timber.d("downloadWatchedMovies: no changes since %tF %tT", lastWatchedAt, lastWatchedAt);
        return UpdateResult.SUCCESS;
    }
    if (!TraktCredentials.get(context).hasCredentials()) {
        return UpdateResult.INCOMPLETE;
    }
    // download watched movies
    List<BaseMovie> watchedMovies;
    try {
        Response<List<BaseMovie>> response = traktSync.get().watchedMovies(Extended.DEFAULT_MIN).execute();
        if (response.isSuccessful()) {
            watchedMovies = response.body();
        } else {
            if (SgTrakt.isUnauthorized(context, response)) {
                return UpdateResult.INCOMPLETE;
            }
            SgTrakt.trackFailedRequest(context, "get watched movies", response);
            return UpdateResult.INCOMPLETE;
        }
    } catch (IOException e) {
        SgTrakt.trackFailedRequest(context, "get watched movies", e);
        return UpdateResult.INCOMPLETE;
    }
    if (watchedMovies == null) {
        Timber.e("downloadWatchedMovies: null response");
        return UpdateResult.INCOMPLETE;
    }
    if (watchedMovies.isEmpty()) {
        Timber.d("downloadWatchedMovies: no watched movies on trakt");
        return UpdateResult.SUCCESS;
    }
    // apply watched flags for all watched trakt movies that are in the local database
    ArrayList<ContentProviderOperation> batch = new ArrayList<>();
    Set<Integer> localMovies = MovieTools.getMovieTmdbIdsAsSet(context);
    if (localMovies == null) {
        return UpdateResult.INCOMPLETE;
    }
    Set<Integer> unwatchedMovies = new HashSet<>(localMovies);
    for (BaseMovie movie : watchedMovies) {
        if (movie.movie == null || movie.movie.ids == null || movie.movie.ids.tmdb == null) {
            // required values are missing
            continue;
        }
        if (!localMovies.contains(movie.movie.ids.tmdb)) {
            // movie NOT in local database
            // add a shell entry for storing watched state
            batch.add(ContentProviderOperation.newInsert(SeriesGuideContract.Movies.CONTENT_URI).withValue(SeriesGuideContract.Movies.TMDB_ID, movie.movie.ids.tmdb).withValue(SeriesGuideContract.Movies.WATCHED, true).withValue(SeriesGuideContract.Movies.IN_COLLECTION, false).withValue(SeriesGuideContract.Movies.IN_WATCHLIST, false).build());
        } else {
            // movie IN local database
            // set movie watched
            batch.add(ContentProviderOperation.newUpdate(SeriesGuideContract.Movies.buildMovieUri(movie.movie.ids.tmdb)).withValue(SeriesGuideContract.Movies.WATCHED, true).build());
            unwatchedMovies.remove(movie.movie.ids.tmdb);
        }
    }
    // remove watched flags from all remaining local movies
    for (Integer tmdbId : unwatchedMovies) {
        batch.add(ContentProviderOperation.newUpdate(SeriesGuideContract.Movies.buildMovieUri(tmdbId)).withValue(SeriesGuideContract.Movies.WATCHED, false).build());
    }
    // apply database updates
    try {
        DBUtils.applyInSmallBatches(context, batch);
    } catch (OperationApplicationException e) {
        Timber.e(e, "downloadWatchedMovies: updating watched flags failed");
        return UpdateResult.INCOMPLETE;
    }
    // save last watched instant
    PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(TraktSettings.KEY_LAST_MOVIES_WATCHED_AT, watchedAt.getMillis()).commit();
    Timber.d("downloadWatchedMovies: success, last watched_at %tF %tT", watchedAt.getMillis(), watchedAt.getMillis());
    return UpdateResult.SUCCESS;
}
Also used : ContentProviderOperation(android.content.ContentProviderOperation) ArrayList(java.util.ArrayList) IOException(java.io.IOException) BaseMovie(com.uwetrottmann.trakt5.entities.BaseMovie) List(java.util.List) ArrayList(java.util.ArrayList) LinkedList(java.util.LinkedList) OperationApplicationException(android.content.OperationApplicationException) HashSet(java.util.HashSet)

Example 4 with Sync

use of com.uwetrottmann.trakt5.services.Sync in project SeriesGuide by UweTrottmann.

the class AddShowTask method doInBackground.

@Override
protected Void doInBackground(Void... params) {
    Timber.d("Starting to add shows...");
    SearchResult firstShow = addQueue.peek();
    if (firstShow == null) {
        Timber.d("Finished. Queue was empty.");
        return null;
    }
    if (!AndroidUtils.isNetworkConnected(context)) {
        Timber.d("Finished. No internet connection.");
        publishProgress(RESULT_OFFLINE, firstShow.getTmdbId(), firstShow.getTitle());
        return null;
    }
    if (isCancelled()) {
        Timber.d("Finished. Cancelled.");
        return null;
    }
    // if not connected to Hexagon, get episodes from trakt
    Map<Integer, BaseShow> traktCollection = null;
    Map<Integer, BaseShow> traktWatched = null;
    if (!HexagonSettings.isEnabled(context) && TraktCredentials.get(context).hasCredentials()) {
        Timber.d("Getting watched and collected episodes from trakt.");
        // get collection
        Map<Integer, BaseShow> traktShows = getTraktShows(true);
        if (traktShows == null) {
            // can not get collected state from trakt, give up.
            return null;
        }
        traktCollection = traktShows;
        // get watched
        traktShows = getTraktShows(false);
        if (traktShows == null) {
            // can not get watched state from trakt, give up.
            return null;
        }
        traktWatched = traktShows;
    }
    HexagonEpisodeSync hexagonEpisodeSync = new HexagonEpisodeSync(context, SgApp.getServicesComponent(context).hexagonTools());
    int result;
    boolean addedAtLeastOneShow = false;
    boolean failedMergingShows = false;
    while (!addQueue.isEmpty()) {
        Timber.d("Starting to add next show...");
        if (isCancelled()) {
            Timber.d("Finished. Cancelled.");
            // table yet
            return null;
        }
        SearchResult nextShow = addQueue.removeFirst();
        // set values required for progress update
        String currentShowName = nextShow.getTitle();
        int currentShowTmdbId = nextShow.getTmdbId();
        if (currentShowTmdbId <= 0) {
            // Invalid ID, should never have been passed, report.
            // Background: Hexagon gets requests with ID 0.
            IllegalStateException invalidIdException = new IllegalStateException("Show id invalid: " + currentShowTmdbId + ", silentMode=" + isSilentMode + ", merging=" + isMergingShows);
            Errors.logAndReport("Add show", invalidIdException);
            continue;
        }
        if (!AndroidUtils.isNetworkConnected(context)) {
            Timber.d("Finished. No connection.");
            publishProgress(RESULT_OFFLINE, currentShowTmdbId, currentShowName);
            failedMergingShows = true;
            break;
        }
        ShowResult addResult = SgApp.getServicesComponent(context).showTools().addShow(nextShow.getTmdbId(), nextShow.getLanguage(), traktCollection, traktWatched, hexagonEpisodeSync);
        if (addResult == ShowResult.SUCCESS) {
            result = PROGRESS_SUCCESS;
            addedAtLeastOneShow = true;
        } else if (addResult == ShowResult.IN_DATABASE) {
            result = PROGRESS_EXISTS;
        } else {
            Timber.e("Adding show failed: %s", addResult);
            // not because it does not (longer) exist.
            if (isMergingShows && addResult != ShowResult.DOES_NOT_EXIST) {
                failedMergingShows = true;
            }
            switch(addResult) {
                case DOES_NOT_EXIST:
                    result = PROGRESS_ERROR_TVDB_NOT_EXISTS;
                    break;
                case TMDB_ERROR:
                    result = PROGRESS_ERROR_TVDB;
                    break;
                case TRAKT_ERROR:
                    result = PROGRESS_ERROR_TRAKT;
                    break;
                case HEXAGON_ERROR:
                    result = PROGRESS_ERROR_HEXAGON;
                    break;
                case DATABASE_ERROR:
                    result = PROGRESS_ERROR_DATA;
                    break;
                default:
                    result = PROGRESS_ERROR;
                    break;
            }
        }
        publishProgress(result, currentShowTmdbId, currentShowName);
        Timber.d("Finished adding show. (Result code: %s)", result);
    }
    isFinishedAddingShows = true;
    // when merging shows down from Hexagon, set success flag
    if (isMergingShows && !failedMergingShows) {
        HexagonSettings.setHasMergedShows(context, true);
    }
    if (addedAtLeastOneShow) {
        // make sure the next sync will download all ratings
        PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(TraktSettings.KEY_LAST_SHOWS_RATED_AT, 0).putLong(TraktSettings.KEY_LAST_EPISODES_RATED_AT, 0).apply();
        // renew FTS3 table
        Timber.d("Renewing search table.");
        SeriesGuideDatabase.rebuildFtsTable(context);
    }
    Timber.d("Finished adding shows.");
    return null;
}
Also used : BaseShow(com.uwetrottmann.trakt5.entities.BaseShow) HexagonEpisodeSync(com.battlelancer.seriesguide.sync.HexagonEpisodeSync) ShowResult(com.battlelancer.seriesguide.ui.shows.ShowTools2.ShowResult) SuppressLint(android.annotation.SuppressLint)

Example 5 with Sync

use of com.uwetrottmann.trakt5.services.Sync in project SeriesGuide by UweTrottmann.

the class BaseShowActionTask method doBackgroundAction.

@Override
protected Integer doBackgroundAction(Void... params) {
    if (isSendingToTrakt()) {
        if (!TraktCredentials.get(getContext()).hasCredentials()) {
            return ERROR_TRAKT_AUTH;
        }
        SyncItems items = new SyncItems().shows(new SyncShow().id(ShowIds.tmdb(showTmdbId)));
        try {
            Sync traktSync = SgApp.getServicesComponent(getContext()).traktSync();
            Response<SyncResponse> response = doTraktAction(traktSync, items).execute();
            if (response.isSuccessful()) {
                if (isShowNotFound(response.body())) {
                    return ERROR_TRAKT_API_NOT_FOUND;
                }
            } else {
                if (SgTrakt.isUnauthorized(getContext(), response)) {
                    return ERROR_TRAKT_AUTH;
                }
                Errors.logAndReport(getTraktAction(), response);
                return ERROR_TRAKT_API;
            }
        } catch (Exception e) {
            Errors.logAndReport(getTraktAction(), e);
            return ERROR_TRAKT_API;
        }
    }
    return SUCCESS;
}
Also used : SyncItems(com.uwetrottmann.trakt5.entities.SyncItems) SyncResponse(com.uwetrottmann.trakt5.entities.SyncResponse) SyncShow(com.uwetrottmann.trakt5.entities.SyncShow) Sync(com.uwetrottmann.trakt5.services.Sync)

Aggregations

OperationApplicationException (android.content.OperationApplicationException)8 ArrayList (java.util.ArrayList)8 List (java.util.List)7 ContentProviderOperation (android.content.ContentProviderOperation)6 IOException (java.io.IOException)4 LinkedList (java.util.LinkedList)4 BaseShow (com.uwetrottmann.trakt5.entities.BaseShow)2 LastActivities (com.uwetrottmann.trakt5.entities.LastActivities)2 RatedEpisode (com.uwetrottmann.trakt5.entities.RatedEpisode)2 RatedMovie (com.uwetrottmann.trakt5.entities.RatedMovie)2 RatedShow (com.uwetrottmann.trakt5.entities.RatedShow)2 SyncItems (com.uwetrottmann.trakt5.entities.SyncItems)2 SyncResponse (com.uwetrottmann.trakt5.entities.SyncResponse)2 Sync (com.uwetrottmann.trakt5.services.Sync)2 HashMap (java.util.HashMap)2 SuppressLint (android.annotation.SuppressLint)1 Cursor (android.database.Cursor)1 SearchResult (com.battlelancer.seriesguide.items.SearchResult)1 HexagonEpisodeSync (com.battlelancer.seriesguide.sync.HexagonEpisodeSync)1 TvdbException (com.battlelancer.seriesguide.thetvdbapi.TvdbException)1