/* * Copyright 2014 Google Inc. 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 com.google.samples.apps.iosched.io; import android.content.ContentProviderOperation; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; import android.text.TextUtils; import com.google.samples.apps.iosched.Config; import com.google.samples.apps.iosched.io.model.Video; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.gson.Gson; import com.google.gson.JsonElement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import static com.google.samples.apps.iosched.util.LogUtils.*; public class VideosHandler extends JSONHandler { private static final String TAG = makeLogTag(VideosHandler.class); private HashMap<String, Video> mVideos = new HashMap<String, Video>(); public VideosHandler(Context context) { super(context); } @Override public void process(JsonElement element) { for (Video video : new Gson().fromJson(element, Video[].class)) { if (TextUtils.isEmpty(video.id)) { LOGW(TAG, "Video without valid ID. Using VID instead: " + video.vid); video.id = video.vid; } mVideos.put(video.id, video); } } @Override public void makeContentProviderOperations(ArrayList<ContentProviderOperation> list) { Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( ScheduleContract.Videos.CONTENT_URI); HashMap<String, String> videoHashcodes = loadVideoHashcodes(); HashSet<String> videosToKeep = new HashSet<String>(); boolean isIncrementalUpdate = videoHashcodes != null && videoHashcodes.size() > 0; if (isIncrementalUpdate) { LOGD(TAG, "Doing incremental update for videos."); } else { LOGD(TAG, "Doing FULL (non incremental) update for videos."); list.add(ContentProviderOperation.newDelete(uri).build()); } int updatedVideos = 0; for (Video video : mVideos.values()) { String hashCode = video.getImportHashcode(); videosToKeep.add(video.id); // add video, if necessary if (!isIncrementalUpdate || !videoHashcodes.containsKey(video.id) || !videoHashcodes.get(video.id).equals(hashCode)) { ++updatedVideos; boolean isNew = !isIncrementalUpdate || !videoHashcodes.containsKey(video.id); buildVideo(isNew, video, list); } } int deletedVideos = 0; if (isIncrementalUpdate) { for (String videoId : videoHashcodes.keySet()) { if (!videosToKeep.contains(videoId)) { buildDeleteOperation(videoId, list); ++deletedVideos; } } } LOGD(TAG, "Videos: " + (isIncrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " + updatedVideos + " to update, " + deletedVideos + " to delete. New total: " + mVideos.size()); } private void buildVideo(boolean isInsert, Video video, ArrayList<ContentProviderOperation> list) { Uri allVideosUri = ScheduleContract.addCallerIsSyncAdapterParameter( ScheduleContract.Videos.CONTENT_URI); Uri thisVideoUri = ScheduleContract.addCallerIsSyncAdapterParameter( ScheduleContract.Videos.buildVideoUri(video.id)); ContentProviderOperation.Builder builder; if (isInsert) { builder = ContentProviderOperation.newInsert(allVideosUri); } else { builder = ContentProviderOperation.newUpdate(thisVideoUri); } if (TextUtils.isEmpty(video.vid)) { LOGW(TAG, "Ignoring video with missing video ID."); return; } String thumbUrl = video.thumbnailUrl; if (TextUtils.isEmpty(thumbUrl)) { // Oops, missing thumbnail URL. Let's improvise. // NOTE: this method of obtaining a thumbnail URL from the video ID // is unofficial and might not work in the future; that's why we use // it only as a fallback in case we don't get a thumbnail URL in the incoming data. thumbUrl = String.format(Locale.US, Config.VIDEO_LIBRARY_FALLBACK_THUMB_URL_FMT, video.vid); LOGW(TAG, "Video with missing thumbnail URL: " + video.vid + ". Using fallback: " + thumbUrl); } list.add(builder.withValue(ScheduleContract.Videos.VIDEO_ID, video.id) .withValue(ScheduleContract.Videos.VIDEO_YEAR, video.year) .withValue(ScheduleContract.Videos.VIDEO_TITLE, video.title.trim()) .withValue(ScheduleContract.Videos.VIDEO_DESC, video.desc) .withValue(ScheduleContract.Videos.VIDEO_VID, video.vid) .withValue(ScheduleContract.Videos.VIDEO_TOPIC, video.topic) .withValue(ScheduleContract.Videos.VIDEO_SPEAKERS, video.speakers) .withValue(ScheduleContract.Videos.VIDEO_THUMBNAIL_URL, thumbUrl) .withValue(ScheduleContract.Videos.VIDEO_IMPORT_HASHCODE, video.getImportHashcode()) .build()); } private void buildDeleteOperation(String videoId, ArrayList<ContentProviderOperation> list) { Uri videoUri = ScheduleContract.addCallerIsSyncAdapterParameter( ScheduleContract.Videos.buildVideoUri(videoId)); list.add(ContentProviderOperation.newDelete(videoUri).build()); } private HashMap<String, String> loadVideoHashcodes() { Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( ScheduleContract.Videos.CONTENT_URI); Cursor cursor = mContext.getContentResolver().query(uri, VideoHashcodeQuery.PROJECTION, null, null, null); if (cursor == null) { LOGE(TAG, "Error querying video hashcodes (got null cursor)"); return null; } if (cursor.getCount() < 1) { LOGE(TAG, "Error querying video hashcodes (no records returned)"); return null; } HashMap<String, String> result = new HashMap<String, String>(); while (cursor.moveToNext()) { String videoId = cursor.getString(VideoHashcodeQuery.VIDEO_ID); String hashcode = cursor.getString(VideoHashcodeQuery.VIDEO_IMPORT_HASHCODE); result.put(videoId, hashcode == null ? "" : hashcode); } cursor.close(); return result; } private interface VideoHashcodeQuery { String[] PROJECTION = { BaseColumns._ID, ScheduleContract.Videos.VIDEO_ID, ScheduleContract.Videos.VIDEO_IMPORT_HASHCODE }; final int _ID = 0; final int VIDEO_ID = 1; final int VIDEO_IMPORT_HASHCODE = 2; } }