/* * Copyright 2015 Synced Synapse. All rights reserved. * * 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 org.xbmc.kore.service.library; import android.content.ContentResolver; import android.content.ContentValues; import android.os.Bundle; import android.os.Handler; import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.ApiList; import org.xbmc.kore.jsonrpc.HostConnection; import org.xbmc.kore.jsonrpc.method.VideoLibrary; import org.xbmc.kore.jsonrpc.type.ListType; import org.xbmc.kore.jsonrpc.type.VideoType; import org.xbmc.kore.provider.MediaContract; import org.xbmc.kore.utils.LogUtils; import java.util.ArrayList; import java.util.List; public class SyncMovies extends SyncItem { public static final String TAG = LogUtils.makeLogTag(SyncMovies.class); private static final int LIMIT_SYNC_MOVIES = 300; private final int hostId; private final int movieId; private final Bundle syncExtras; /** * Syncs all the movies on selected XBMC to the local database * @param hostId XBMC host id */ public SyncMovies(final int hostId, Bundle syncExtras) { this.hostId = hostId; this.movieId = -1; this.syncExtras = syncExtras; } /** * Syncs a specific movie on selected XBMC to the local database * @param hostId XBMC host id */ public SyncMovies(final int hostId, final int movieId, Bundle syncExtras) { this.hostId = hostId; this.movieId = movieId; this.syncExtras = syncExtras; } /** {@inheritDoc} */ public String getDescription() { return (movieId != -1) ? "Sync movies for host: " + hostId : "Sync movie " + movieId + " for host: " + hostId; } /** {@inheritDoc} */ public String getSyncType() { return (movieId == -1) ? LibrarySyncService.SYNC_ALL_MOVIES : LibrarySyncService.SYNC_SINGLE_MOVIE; } /** {@inheritDoc} */ public Bundle getSyncExtras() { return syncExtras; } /** {@inheritDoc} */ public void sync(final SyncOrchestrator orchestrator, final HostConnection hostConnection, final Handler callbackHandler, final ContentResolver contentResolver) { String properties[] = { VideoType.FieldsMovie.TITLE, VideoType.FieldsMovie.GENRE, VideoType.FieldsMovie.YEAR, VideoType.FieldsMovie.RATING, VideoType.FieldsMovie.DIRECTOR, VideoType.FieldsMovie.TRAILER, VideoType.FieldsMovie.TAGLINE, VideoType.FieldsMovie.PLOT, // VideoType.FieldsMovie.PLOTOUTLINE, VideoType.FieldsMovie.ORIGINALTITLE, // VideoType.FieldsMovie.LASTPLAYED, VideoType.FieldsMovie.PLAYCOUNT, VideoType.FieldsMovie.DATEADDED, VideoType.FieldsMovie.WRITER, VideoType.FieldsMovie.STUDIO, VideoType.FieldsMovie.MPAA, VideoType.FieldsMovie.CAST, VideoType.FieldsMovie.COUNTRY, VideoType.FieldsMovie.IMDBNUMBER, VideoType.FieldsMovie.RUNTIME, VideoType.FieldsMovie.SET, // VideoType.FieldsMovie.SHOWLINK, VideoType.FieldsMovie.STREAMDETAILS, VideoType.FieldsMovie.TOP250, VideoType.FieldsMovie.VOTES, VideoType.FieldsMovie.FANART, VideoType.FieldsMovie.THUMBNAIL, VideoType.FieldsMovie.FILE, // VideoType.FieldsMovie.SORTTITLE, VideoType.FieldsMovie.RESUME, VideoType.FieldsMovie.SETID, // VideoType.FieldsMovie.DATEADDED, VideoType.FieldsMovie.TAG, // VideoType.FieldsMovie.ART }; if (movieId == -1) { syncAllMovies(orchestrator, hostConnection, callbackHandler, contentResolver, properties, 0); } else { // Sync a specific movie VideoLibrary.GetMovieDetails action = new VideoLibrary.GetMovieDetails(movieId, properties); action.execute(hostConnection, new ApiCallback<VideoType.DetailsMovie>() { @Override public void onSuccess(VideoType.DetailsMovie result) { deleteMovies(contentResolver, hostId, movieId); List<VideoType.DetailsMovie> movies = new ArrayList<VideoType.DetailsMovie>(1); movies.add(result); insertMovies(orchestrator, contentResolver, movies); orchestrator.syncItemFinished(); } @Override public void onError(int errorCode, String description) { // Ok, something bad happend, just quit orchestrator.syncItemFailed(errorCode, description); } }, callbackHandler); } } /** * Syncs all the movies, calling itself recursively * Uses the {@link VideoLibrary.GetMovies} version with limits to make sure * that Kodi doesn't blow up, and calls itself recursively until all the * movies are returned */ private void syncAllMovies(final SyncOrchestrator orchestrator, final HostConnection hostConnection, final Handler callbackHandler, final ContentResolver contentResolver, final String properties[], final int startIdx) { // Call GetMovies with the current limits set ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_MOVIES); VideoLibrary.GetMovies action = new VideoLibrary.GetMovies(limits, properties); action.execute(hostConnection, new ApiCallback<ApiList<VideoType.DetailsMovie>>() { @Override public void onSuccess(ApiList<VideoType.DetailsMovie> result) { ListType.LimitsReturned limitsReturned = null; if (result != null) { limitsReturned = result.limits; } if (startIdx == 0) { // First call, delete movies from DB deleteMovies(contentResolver, hostId, -1); } if (!result.items.isEmpty()) { insertMovies(orchestrator, contentResolver, result.items); } LogUtils.LOGD(TAG, "syncAllMovies, movies gotten: " + result.items.size()); if (SyncUtils.moreItemsAvailable(limitsReturned)) { // Max limit returned, there may be some more movies // As we're going to recurse, these result objects can add up, so // let's help the GC and indicate that we don't need this memory // (hopefully this works) result = null; syncAllMovies(orchestrator, hostConnection, callbackHandler, contentResolver, properties, startIdx + LIMIT_SYNC_MOVIES); } else { // Less than the limit was returned so we can finish // (if it returned more there's a bug in Kodi but it // shouldn't be a problem as they got inserted in the DB) orchestrator.syncItemFinished(); } } @Override public void onError(int errorCode, String description) { // Ok, something bad happened, just quit orchestrator.syncItemFailed(errorCode, description); } }, callbackHandler); } /** * Deletes one or all movies from the database (pass -1 on movieId to delete all) */ private void deleteMovies(final ContentResolver contentResolver, int hostId, int movieId) { if (movieId == -1) { // Delete all movies String where = MediaContract.MoviesColumns.HOST_ID + "=?"; contentResolver.delete(MediaContract.MovieCast.CONTENT_URI, where, new String[]{String.valueOf(hostId)}); contentResolver.delete(MediaContract.Movies.CONTENT_URI, where, new String[]{String.valueOf(hostId)}); } else { // Delete a movie contentResolver.delete(MediaContract.MovieCast.buildMovieCastListUri(hostId, movieId), null, null); contentResolver.delete(MediaContract.Movies.buildMovieUri(hostId, movieId), null, null); } } /** * Inserts the given movies in the database */ private void insertMovies(final SyncOrchestrator orchestrator, final ContentResolver contentResolver, final List<VideoType.DetailsMovie> movies) { ContentValues movieValuesBatch[] = new ContentValues[movies.size()]; int castCount = 0; // Iterate on each movie for (int i = 0; i < movies.size(); i++) { VideoType.DetailsMovie movie = movies.get(i); movieValuesBatch[i] = SyncUtils.contentValuesFromMovie(hostId, movie); castCount += movie.cast.size(); } // Insert the movies contentResolver.bulkInsert(MediaContract.Movies.CONTENT_URI, movieValuesBatch); ContentValues movieCastValuesBatch[] = new ContentValues[castCount]; int count = 0; // Iterate on each movie/cast for (VideoType.DetailsMovie movie : movies) { for (VideoType.Cast cast : movie.cast) { movieCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast); movieCastValuesBatch[count].put(MediaContract.MovieCastColumns.MOVIEID, movie.movieid); count++; } } // Insert the cast list for this movie contentResolver.bulkInsert(MediaContract.MovieCast.CONTENT_URI, movieCastValuesBatch); } }