/* * Copyright (C) 2015 Simon Vig Therkildsen * * 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 net.simonvt.cathode.remote.sync.comments; import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.OperationApplicationException; import android.database.Cursor; import android.os.RemoteException; import android.support.v4.util.LongSparseArray; import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import net.simonvt.cathode.api.Trakt; import net.simonvt.cathode.api.entity.Comment; import net.simonvt.cathode.api.entity.CommentItem; import net.simonvt.cathode.api.entity.Episode; import net.simonvt.cathode.api.entity.Movie; import net.simonvt.cathode.api.entity.Profile; import net.simonvt.cathode.api.entity.Season; import net.simonvt.cathode.api.enumeration.CommentType; import net.simonvt.cathode.api.enumeration.ItemTypes; import net.simonvt.cathode.api.service.UsersService; import net.simonvt.cathode.jobqueue.JobFailedException; import net.simonvt.cathode.provider.CommentsHelper; import net.simonvt.cathode.provider.DatabaseContract; import net.simonvt.cathode.provider.DatabaseContract.CommentColumns; import net.simonvt.cathode.provider.EpisodeDatabaseHelper; import net.simonvt.cathode.provider.MovieDatabaseHelper; import net.simonvt.cathode.provider.ProviderSchematic.Comments; import net.simonvt.cathode.provider.SeasonDatabaseHelper; import net.simonvt.cathode.provider.ShowDatabaseHelper; import net.simonvt.cathode.provider.UserDatabaseHelper; import net.simonvt.cathode.provider.generated.CathodeProvider; import net.simonvt.cathode.remote.Flags; import net.simonvt.cathode.remote.PagedCallJob; import net.simonvt.cathode.remote.sync.SyncUserProfile; import net.simonvt.cathode.remote.sync.movies.SyncMovie; import net.simonvt.cathode.remote.sync.shows.SyncSeason; import net.simonvt.cathode.remote.sync.shows.SyncShow; import net.simonvt.schematic.Cursors; import retrofit2.Call; import timber.log.Timber; public class SyncUserComments extends PagedCallJob<CommentItem> { public static class DuplicateCommentException extends Exception { } private static final int LIMIT = 100; @Inject transient UsersService usersService; @Inject transient ShowDatabaseHelper showHelper; @Inject transient SeasonDatabaseHelper seasonHelper; @Inject transient EpisodeDatabaseHelper episodeHelper; @Inject transient MovieDatabaseHelper movieHelper; @Inject transient UserDatabaseHelper userHelper; @Inject @Trakt transient Gson gson; private ItemTypes itemTypes; public SyncUserComments(ItemTypes itemTypes) { super(Flags.REQUIRES_AUTH); this.itemTypes = itemTypes; } @Override public String key() { return "SyncUserComments&types=" + itemTypes; } @Override public int getPriority() { return PRIORITY_USER_DATA; } @Override public Call<List<CommentItem>> getCall(int page) { return usersService.getUserComments(CommentType.ALL, itemTypes, page, LIMIT); } @Override public void handleResponse(List<CommentItem> comments) { List<Long> existingComments = new ArrayList<>(); LongSparseArray<CommentItem> addedLater = new LongSparseArray<>(); Cursor c = getContentResolver().query(Comments.COMMENTS, new String[] { CommentColumns.ID, }, CommentColumns.IS_USER_COMMENT + "=1", null, null); while (c.moveToNext()) { final long id = Cursors.getLong(c, CommentColumns.ID); existingComments.add(id); } c.close(); ArrayList<ContentProviderOperation> ops = new ArrayList<>(); long profileId = -1L; for (CommentItem commentItem : comments) { Comment comment = commentItem.getComment(); final long commentId = comment.getId(); if (addedLater.get(commentId) != null) { CommentItem otherCommentItem = addedLater.get(commentId); String otherComment = gson.toJson(otherCommentItem); String thisComment = gson.toJson(commentItem); Timber.i("Other comment: %s", otherComment); Timber.i("Comment: %s", thisComment); Timber.e(new DuplicateCommentException(), "Comment with id %d appears twice in result", commentId); continue; } ContentValues values = CommentsHelper.getValues(comment); values.put(CommentColumns.IS_USER_COMMENT, true); if (profileId == -1L) { Profile profile = commentItem.getComment().getUser(); UserDatabaseHelper.IdResult result = userHelper.updateOrCreate(profile); profileId = result.id; queue(new SyncUserProfile()); } values.put(CommentColumns.USER_ID, profileId); switch (commentItem.getType()) { case SHOW: { final long traktId = commentItem.getShow().getIds().getTrakt(); ShowDatabaseHelper.IdResult result = showHelper.getIdOrCreate(traktId); final long showId = result.showId; if (result.didCreate) { queue(new SyncShow(traktId)); } values.put(CommentColumns.ITEM_TYPE, DatabaseContract.ItemType.SHOW); values.put(CommentColumns.ITEM_ID, showId); break; } case SEASON: { final long traktId = commentItem.getShow().getIds().getTrakt(); ShowDatabaseHelper.IdResult showResult = showHelper.getIdOrCreate(traktId); final long showId = showResult.showId; if (showResult.didCreate) { queue(new SyncShow(traktId)); } Season season = commentItem.getSeason(); final int seasonNumber = season.getNumber(); SeasonDatabaseHelper.IdResult seasonResult = seasonHelper.getIdOrCreate(showId, seasonNumber); final long seasonId = seasonResult.id; if (seasonResult.didCreate) { if (!showResult.didCreate) { queue(new SyncShow(traktId)); } } values.put(CommentColumns.ITEM_TYPE, DatabaseContract.ItemType.SEASON); values.put(CommentColumns.ITEM_ID, seasonId); break; } case EPISODE: { final long traktId = commentItem.getShow().getIds().getTrakt(); ShowDatabaseHelper.IdResult showResult = showHelper.getIdOrCreate(traktId); final long showId = showResult.showId; if (showResult.didCreate) { queue(new SyncShow(traktId)); } Episode episode = commentItem.getEpisode(); final int seasonNumber = episode.getSeason(); final int episodeNumber = episode.getNumber(); SeasonDatabaseHelper.IdResult seasonResult = seasonHelper.getIdOrCreate(showId, seasonNumber); final long seasonId = seasonResult.id; if (seasonResult.didCreate) { if (!showResult.didCreate) { queue(new SyncShow(traktId)); } } EpisodeDatabaseHelper.IdResult episodeResult = episodeHelper.getIdOrCreate(showId, seasonId, episodeNumber); final long episodeId = episodeResult.id; if (episodeResult.didCreate) { if (!showResult.didCreate && !seasonResult.didCreate) { queue(new SyncSeason(traktId, seasonNumber)); } } values.put(CommentColumns.ITEM_TYPE, DatabaseContract.ItemType.EPISODE); values.put(CommentColumns.ITEM_ID, episodeId); break; } case MOVIE: { Movie movie = commentItem.getMovie(); final long traktId = movie.getIds().getTrakt(); MovieDatabaseHelper.IdResult result = movieHelper.getIdOrCreate(traktId); final long movieId = result.movieId; if (result.didCreate) { queue(new SyncMovie(traktId)); } values.put(CommentColumns.ITEM_TYPE, DatabaseContract.ItemType.MOVIE); values.put(CommentColumns.ITEM_ID, movieId); break; } case LIST: continue; } boolean exists = existingComments.contains(commentId); if (!exists) { // May have been created by user likes c = getContentResolver().query(Comments.withId(commentId), new String[] { CommentColumns.ID, }, null, null, null); exists = c.moveToFirst(); c.close(); } if (exists) { existingComments.remove(commentId); ContentProviderOperation.Builder op = ContentProviderOperation.newUpdate(Comments.withId(commentId)).withValues(values); ops.add(op.build()); } else { ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(Comments.COMMENTS).withValues(values); ops.add(op.build()); addedLater.put(commentId, commentItem); } } for (Long id : existingComments) { ContentProviderOperation.Builder op = ContentProviderOperation.newDelete(Comments.withId(id)); ops.add(op.build()); } try { getContentResolver().applyBatch(CathodeProvider.AUTHORITY, ops); } catch (RemoteException e) { Timber.e(e, "Updating comments failed"); throw new JobFailedException(e); } catch (OperationApplicationException e) { Timber.e(e, "Updating comments failed"); throw new JobFailedException(e); } } }