ch.berta.fabio.popularmovies.data.repositories.MovieRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.berta.fabio.popularmovies.data.repositories.MovieRepositoryImpl.java

Source

/*
 * Copyright (c) 2015 Fabio Berta
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ch.berta.fabio.popularmovies.data.repositories;

import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.v4.content.CursorLoader;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import ch.berta.fabio.popularmovies.R;
import ch.berta.fabio.popularmovies.domain.models.Movie;
import ch.berta.fabio.popularmovies.domain.models.MovieDetails;
import ch.berta.fabio.popularmovies.domain.models.MoviesPage;
import ch.berta.fabio.popularmovies.domain.models.Review;
import ch.berta.fabio.popularmovies.domain.models.Video;
import ch.berta.fabio.popularmovies.data.rest.MovieDbClient;
import ch.berta.fabio.popularmovies.data.storage.MovieContract;
import ch.berta.fabio.popularmovies.domain.repositories.MovieRepository;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.exceptions.Exceptions;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

/**
 * Provides an implementation of the {@link MovieRepository} interface.
 */
public class MovieRepositoryImpl implements MovieRepository {

    private static final String LOG_TAG = MovieRepositoryImpl.class.getSimpleName();
    private static final int COL_INDEX_MOVIE_ROW_ID = 0;
    private static final int COL_INDEX_MOVIE_DB_ID = 1;
    private static final int COL_INDEX_MOVIE_TITLE = 2;
    private static final int COL_INDEX_MOVIE_RELEASE_DATE = 3;
    private static final int COL_INDEX_MOVIE_POSTER = 4;
    private static final String[] FAV_MOVIES_COLUMNS = new String[] { MovieContract.Movie._ID,
            MovieContract.Movie.COLUMN_DB_ID, MovieContract.Movie.COLUMN_TITLE,
            MovieContract.Movie.COLUMN_RELEASE_DATE, MovieContract.Movie.COLUMN_POSTER, };

    private static final int COL_INDEX_MOVIE_DETAILS_DB_ID = 0;
    private static final int COL_INDEX_MOVIE_DETAILS_TITLE = 1;
    private static final int COL_INDEX_MOVIE_DETAILS_RELEASE_DATE = 2;
    private static final int COL_INDEX_MOVIE_DETAILS_VOTE_AVERAGE = 3;
    private static final int COL_INDEX_MOVIE_DETAILS_PLOT = 4;
    private static final int COL_INDEX_MOVIE_DETAILS_POSTER = 5;
    private static final int COL_INDEX_MOVIE_DETAILS_BACKDROP = 6;
    private static final int COL_INDEX_MOVIE_DETAILS_REVIEW_ID = 7;
    private static final int COL_INDEX_MOVIE_DETAILS_REVIEW_AUTHOR = 8;
    private static final int COL_INDEX_MOVIE_DETAILS_REVIEW_CONTENT = 9;
    private static final int COL_INDEX_MOVIE_DETAILS_REVIEW_URL = 10;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_ID = 11;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_NAME = 12;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_KEY = 13;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_SITE = 14;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_SIZE = 15;
    private static final int COL_INDEX_MOVIE_DETAILS_VIDEO_TYPE = 16;
    private static final String[] FAV_MOVIE_DETAILS_COLUMNS = new String[] { MovieContract.Movie.COLUMN_DB_ID,
            MovieContract.Movie.COLUMN_TITLE, MovieContract.Movie.COLUMN_RELEASE_DATE,
            MovieContract.Movie.COLUMN_VOTE_AVERAGE, MovieContract.Movie.COLUMN_PLOT,
            MovieContract.Movie.COLUMN_POSTER, MovieContract.Movie.COLUMN_BACKDROP,
            MovieContract.Review.TABLE_NAME + "." + MovieContract.Review._ID, MovieContract.Review.COLUMN_AUTHOR,
            MovieContract.Review.COLUMN_CONTENT, MovieContract.Review.COLUMN_URL,
            MovieContract.Video.TABLE_NAME + "." + MovieContract.Video._ID, MovieContract.Video.COLUMN_NAME,
            MovieContract.Video.COLUMN_KEY, MovieContract.Video.COLUMN_SITE, MovieContract.Video.COLUMN_SIZE,
            MovieContract.Video.COLUMN_TYPE };

    /**
     * Constructs a new {@link MovieRepositoryImpl}.
     */
    public MovieRepositoryImpl() {
    }

    @Override
    public Observable<List<Movie>> getMoviesOnline(@NonNull Context context, int page, @NonNull String sort) {
        return MovieDbClient.getService().loadMoviePosters(page, sort, context.getString(R.string.movie_db_key))
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<MoviesPage, List<Movie>>() {
                    @Override
                    public List<Movie> call(MoviesPage moviesPage) {
                        return moviesPage.getMovies();
                    }
                });
    }

    @Override
    public Observable<MovieDetails> getMovieDetailsOnline(@NonNull Context context, int movieDbId) {
        return MovieDbClient.getService()
                .loadMovieDetails(movieDbId, context.getString(R.string.movie_db_key), "reviews,videos")
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    @Override
    public CursorLoader getFavMoviesLoader(@NonNull Context context) {
        return new CursorLoader(context, MovieContract.Movie.CONTENT_URI, FAV_MOVIES_COLUMNS, null, null,
                MovieContract.Movie.SORT_BY_RELEASE_DATE);
    }

    @Override
    public int getMovieDbIdFromFavMoviesCursor(@NonNull Cursor cursor) {
        return cursor.getInt(MovieRepositoryImpl.COL_INDEX_MOVIE_DB_ID);
    }

    @Override
    public String getMovieTitleFromFavMoviesCursor(@NonNull Cursor cursor) {
        return cursor.getString(MovieRepositoryImpl.COL_INDEX_MOVIE_TITLE);
    }

    @Override
    public Date getMovieReleaseDateFromFavMoviesCursor(@NonNull Cursor cursor) {
        return new Date(cursor.getLong(MovieRepositoryImpl.COL_INDEX_MOVIE_RELEASE_DATE));
    }

    @Override
    public String getMoviePosterFromFavMoviesCursor(@NonNull Cursor cursor) {
        return cursor.getString(MovieRepositoryImpl.COL_INDEX_MOVIE_POSTER);
    }

    @Override
    public CursorLoader getFavMovieDetailsLoader(@NonNull Context context, long movieRowId) {
        return new CursorLoader(context, MovieContract.Movie.buildMovieWithReviewsAndTrailersUri(movieRowId),
                FAV_MOVIE_DETAILS_COLUMNS, null, null, MovieContract.Movie.SORT_DEFAULT);
    }

    @Override
    public Movie getMovieFromFavMovieDetailsCursor(@NonNull Cursor cursor) {
        final int dbId = cursor.getInt(COL_INDEX_MOVIE_DETAILS_DB_ID);
        final String title = cursor.getString(COL_INDEX_MOVIE_DETAILS_TITLE);
        final Date releaseDate = new Date(cursor.getLong(COL_INDEX_MOVIE_DETAILS_RELEASE_DATE));
        final String poster = cursor.getString(COL_INDEX_MOVIE_DETAILS_POSTER);
        final String backdrop = cursor.getString(COL_INDEX_MOVIE_DETAILS_BACKDROP);
        final String plot = cursor.getString(COL_INDEX_MOVIE_DETAILS_PLOT);
        final double rating = cursor.getDouble(COL_INDEX_MOVIE_DETAILS_VOTE_AVERAGE);

        List<Review> reviews = new ArrayList<>();
        int reviewIdCounter = 0;
        List<Video> videos = new ArrayList<>();
        int videoIdCounter = 0;
        do {
            if (!cursor.isNull(COL_INDEX_MOVIE_DETAILS_REVIEW_ID)) {
                final int id = cursor.getInt(COL_INDEX_MOVIE_DETAILS_REVIEW_ID);
                final String author = cursor.getString(COL_INDEX_MOVIE_DETAILS_REVIEW_AUTHOR);
                final String content = cursor.getString(COL_INDEX_MOVIE_DETAILS_REVIEW_CONTENT);
                final String url = cursor.getString(COL_INDEX_MOVIE_DETAILS_REVIEW_URL);

                if (id > reviewIdCounter) {
                    reviewIdCounter = id;
                    reviews.add(new Review(author, content, url));
                }
            }

            if (!cursor.isNull(COL_INDEX_MOVIE_DETAILS_VIDEO_ID)) {
                final int id = cursor.getInt(COL_INDEX_MOVIE_DETAILS_VIDEO_ID);
                final String name = cursor.getString(COL_INDEX_MOVIE_DETAILS_VIDEO_NAME);
                final String key = cursor.getString(COL_INDEX_MOVIE_DETAILS_VIDEO_KEY);
                final String site = cursor.getString(COL_INDEX_MOVIE_DETAILS_VIDEO_SITE);
                final int size = cursor.getInt(COL_INDEX_MOVIE_DETAILS_VIDEO_SIZE);
                final String type = cursor.getString(COL_INDEX_MOVIE_DETAILS_VIDEO_TYPE);

                if (id > videoIdCounter) {
                    videoIdCounter = id;
                    videos.add(new Video(name, key, site, size, type));
                }
            }
        } while (cursor.moveToNext());

        return new Movie(videos, backdrop, dbId, plot, releaseDate, poster, title, rating, reviews, true);
    }

    @Override
    public CursorLoader getIsFavLoader(@NonNull Context context, int movieDbId) {
        return new CursorLoader(context, MovieContract.Movie.buildMovieByDbIdUri(movieDbId),
                new String[] { MovieContract.Movie._ID }, null, null, MovieContract.Movie.SORT_DEFAULT);
    }

    @Override
    public long getRowIdFromIsFavCursor(@NonNull Cursor cursor) {
        return cursor.getLong(0);
    }

    @Override
    public Observable<ContentProviderResult[]> insertMovieLocal(@NonNull final Context context,
            @NonNull Movie movie) {
        return Observable.just(movie).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<Movie, ArrayList<ContentProviderOperation>>() {
                    @Override
                    public ArrayList<ContentProviderOperation> call(Movie movie) {
                        return getInsertContentProviderOps(movie);
                    }
                }).map(new Func1<ArrayList<ContentProviderOperation>, ContentProviderResult[]>() {
                    @Override
                    public ContentProviderResult[] call(ArrayList<ContentProviderOperation> ops) {
                        try {
                            final ContentResolver contentResolver = context.getApplicationContext()
                                    .getContentResolver();
                            return contentResolver.applyBatch(MovieContract.CONTENT_AUTHORITY, ops);
                        } catch (Throwable t) {
                            throw Exceptions.propagate(t);
                        }
                    }
                });
    }

    private ArrayList<ContentProviderOperation> getInsertContentProviderOps(@NonNull Movie movie) {
        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
        ops.add(ContentProviderOperation.newInsert(MovieContract.Movie.CONTENT_URI)
                .withValues(movie.getContentValuesEntry()).build());

        List<Review> reviews = movie.getReviews();
        if (!reviews.isEmpty()) {
            for (Review review : reviews) {
                ops.add(ContentProviderOperation.newInsert(MovieContract.Review.CONTENT_URI)
                        .withValueBackReference(MovieContract.Review.COLUMN_MOVIE_ID, 0)
                        .withValues(review.getContentValuesEntry()).build());
            }
        }

        List<Video> videos = movie.getVideos();
        if (!videos.isEmpty()) {
            for (Video video : videos) {
                // only add youtube videos
                if (video.siteIsYouTube()) {
                    ops.add(ContentProviderOperation.newInsert(MovieContract.Video.CONTENT_URI)
                            .withValueBackReference(MovieContract.Video.COLUMN_MOVIE_ID, 0)
                            .withValues(video.getContentValuesEntry()).build());
                }
            }
        }

        return ops;
    }

    @Override
    public Observable<Integer> deleteMovieLocal(@NonNull final Context context, long movieRowId) {
        return Observable.just(movieRowId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<Long, Integer>() {
                    @Override
                    public Integer call(Long aLong) {
                        final ContentResolver contentResolver = context.getApplicationContext()
                                .getContentResolver();
                        return contentResolver.delete(MovieContract.Movie.buildMovieUri(aLong), null, null);
                    }
                });
    }

    @Override
    public Observable<ContentProviderResult[]> updateMovieLocal(@NonNull final Context context,
            @NonNull final MovieDetails movieDetails, final long movieRowId) {
        return Observable.just(movieDetails).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<MovieDetails, ArrayList<ContentProviderOperation>>() {
                    @Override
                    public ArrayList<ContentProviderOperation> call(MovieDetails movieDetails) {
                        return getUpdateContentProvidersOps(movieDetails, movieRowId);
                    }
                }).map(new Func1<ArrayList<ContentProviderOperation>, ContentProviderResult[]>() {
                    @Override
                    public ContentProviderResult[] call(
                            ArrayList<ContentProviderOperation> contentProviderOperations) {
                        try {
                            final ContentResolver contentResolver = context.getApplicationContext()
                                    .getContentResolver();
                            return contentResolver.applyBatch(MovieContract.CONTENT_AUTHORITY,
                                    contentProviderOperations);
                        } catch (Throwable t) {
                            throw Exceptions.propagate(t);
                        }
                    }
                });
    }

    private ArrayList<ContentProviderOperation> getUpdateContentProvidersOps(@NonNull MovieDetails movieDetails,
            long movieRowId) {
        final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
        addMovieOps(movieDetails, movieRowId, ops);
        addMovieReviewsOps(movieDetails, movieRowId, ops);
        addMovieVideosOps(movieDetails, movieRowId, ops);

        return ops;
    }

    private void addMovieOps(@NonNull MovieDetails movieDetails, long movieRowId,
            @NonNull ArrayList<ContentProviderOperation> ops) {
        ops.add(ContentProviderOperation.newUpdate(MovieContract.Movie.buildMovieUri(movieRowId))
                .withValues(movieDetails.getContentValuesEntry()).build());
    }

    private void addMovieReviewsOps(@NonNull MovieDetails movieDetails, long movieRowId,
            @NonNull ArrayList<ContentProviderOperation> ops) {
        ops.add(ContentProviderOperation.newDelete(MovieContract.Review.buildReviewsFromMovieUri(movieRowId))
                .build());

        List<Review> reviews = movieDetails.getReviewsPage().getReviews();
        if (!reviews.isEmpty()) {
            for (Review review : reviews) {
                ops.add(ContentProviderOperation.newInsert(MovieContract.Review.CONTENT_URI)
                        .withValue(MovieContract.Review.COLUMN_MOVIE_ID, movieRowId)
                        .withValues(review.getContentValuesEntry()).build());
            }
        }
    }

    private void addMovieVideosOps(@NonNull MovieDetails movieDetails, long movieRowId,
            @NonNull ArrayList<ContentProviderOperation> ops) {
        ops.add(ContentProviderOperation.newDelete(MovieContract.Video.buildVideosFromMovieUri(movieRowId))
                .build());

        List<Video> videos = movieDetails.getVideosPage().getVideos();
        if (!videos.isEmpty()) {
            for (Video video : videos) {
                // only add youtube videos
                if (video.siteIsYouTube()) {
                    ops.add(ContentProviderOperation.newInsert(MovieContract.Video.CONTENT_URI)
                            .withValue(MovieContract.Video.COLUMN_MOVIE_ID, movieRowId)
                            .withValues(video.getContentValuesEntry()).build());
                }
            }
        }
    }
}