/* * Copyright (C) 2005-2015 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC Remote; see the file license. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ package org.xbmc.android.app.provider; import android.content.*; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; import org.xbmc.android.util.google.SelectionBuilder; import java.util.ArrayList; import java.util.Arrays; import static org.xbmc.android.app.provider.VideoContract.*; /** * Provider that stores {@link org.xbmc.android.app.provider.VideoContract} data. Data is usually inserted * by {@link org.xbmc.android.app.service.SyncService}, and queried by various {@link android.app.Activity} instances. * <p> * This class, along with the other ones in this package was closely inspired by * Google's official iosched app, see http://code.google.com/p/iosched/ * * @author freezy <freezy@xbmc.org> */ public class VideoProvider extends AbstractProvider { private static final String TAG = VideoProvider.class.getSimpleName(); private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); private VideoDatabase database; private static final UriMatcher URI_MATCHER = buildUriMatcher(); private static final int MOVIES = 100; private static final int MOVIES_ID = 101; private static final int MOVIES_MOVIECAST = 110; private static final int PEOPLE = 200; private static final int PERSON_ID = 201; private static final int MOVIECAST = 210; private static final int MOVIEDIRECTOR = 220; private static final int MOVIEWRITER = 230; private static final int GENRES = 300; private static final int MOVIEGENRES = 310; /** * Build and return a {@link android.content.UriMatcher} that catches all {@link android.net.Uri} * variations supported by this {@link android.content.ContentProvider}. */ private static UriMatcher buildUriMatcher() { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); final String authority = VideoContract.CONTENT_AUTHORITY; matcher.addURI(authority, VideoContract.PATH_MOVIES, MOVIES); matcher.addURI(authority, VideoContract.PATH_MOVIES + "/*", MOVIES_ID); matcher.addURI(authority, VideoContract.PATH_MOVIES + "/*/" + VideoContract.PATH_MOVIECAST, MOVIES_MOVIECAST); matcher.addURI(authority, VideoContract.PATH_PEOPLE, PEOPLE); matcher.addURI(authority, VideoContract.PATH_PEOPLE + "/*", PERSON_ID); matcher.addURI(authority, VideoContract.PATH_MOVIECAST, MOVIECAST); matcher.addURI(authority, VideoContract.PATH_MOVIEDIRECTOR, MOVIEDIRECTOR); matcher.addURI(authority, VideoContract.PATH_MOVIEWRITER, MOVIEWRITER); matcher.addURI(authority, VideoContract.PATH_GENRES, GENRES); matcher.addURI(authority, VideoContract.PATH_MOVIEGENRES, MOVIEGENRES); return matcher; } @Override public boolean onCreate() { database = new VideoDatabase(getContext()); return super.onCreate(); } @Override public String getType(Uri uri) { final int match = URI_MATCHER.match(uri); switch (match) { case MOVIES: return Movies.CONTENT_TYPE; case MOVIES_ID: return Movies.CONTENT_ITEM_TYPE; case PEOPLE: return People.CONTENT_TYPE; case PERSON_ID: return People.CONTENT_ITEM_TYPE; case MOVIECAST: return MovieCast.CONTENT_TYPE; case MOVIEDIRECTOR: return MovieDirector.CONTENT_TYPE; case MOVIEWRITER: return MovieWriter.CONTENT_TYPE; case GENRES: return Genres.CONTENT_TYPE; case MOVIEGENRES: return MovieGenres.CONTENT_TYPE; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (LOGV) Log.v(TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); final SQLiteDatabase db = database.getReadableDatabase(); final int match = URI_MATCHER.match(uri); switch (match) { default: { // Most cases are handled with simple SelectionBuilder final SelectionBuilder builder = buildExpandedSelection(uri, match); return builder.where(selection, selectionArgs).query(db, projection, sortOrder); } } } @Override public Uri insert(Uri uri, ContentValues values) { if (LOGV) Log.v(TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = database.getWritableDatabase(); final int match = URI_MATCHER.match(uri); switch (match) { case MOVIES: { db.insertOrThrow(VideoDatabase.Tables.MOVIES, null, values); getContext().getContentResolver().notifyChange(uri, null); return Movies.buildMovieUri(values.getAsString(Movies.ID)); } case PEOPLE: { final long id = db.insertOrThrow(VideoDatabase.Tables.PEOPLE, null, values); getContext().getContentResolver().notifyChange(uri, null); return People.buildPersonUri(id); } case GENRES: { final long id = db.insertOrThrow(VideoDatabase.Tables.GENRES, null, values); getContext().getContentResolver().notifyChange(uri, null); return Genres.buildGenreUri(id); } case MOVIECAST: { db.insertOrThrow(VideoDatabase.Tables.PEOPLE_MOVIE_CAST, null, values); return null; } case MOVIEDIRECTOR: { db.insertOrThrow(VideoDatabase.Tables.PEOPLE_MOVIE_DIRECTOR, null, values); return null; } case MOVIEWRITER: { db.insertOrThrow(VideoDatabase.Tables.PEOPLE_MOVIE_WRITER, null, values); return null; } case MOVIEGENRES: { db.insertOrThrow(VideoDatabase.Tables.GENRES_MOVIE, null, values); return null; } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } /** {@inheritDoc} */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { if (LOGV) Log.v(TAG, "update(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = database.getWritableDatabase(); final SelectionBuilder builder = buildSimpleSelection(uri); int retVal = builder.where(selection, selectionArgs).update(db, values); getContext().getContentResolver().notifyChange(uri, null); return retVal; } /** {@inheritDoc} */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { if (LOGV) Log.v(TAG, "delete(uri=" + uri + ")"); final SQLiteDatabase db = database.getWritableDatabase(); final SelectionBuilder builder = buildSimpleSelection(uri); int retVal = builder.where(selection, selectionArgs).delete(db); getContext().getContentResolver().notifyChange(uri, null); return retVal; } /** * Apply the given set of {@link android.content.ContentProviderOperation}, executing inside * a {@link android.database.sqlite.SQLiteDatabase} transaction. All changes will be rolled back if * any single one fails. */ @Override public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { final SQLiteDatabase db = database.getWritableDatabase(); db.beginTransaction(); try { final int numOperations = operations.size(); final ContentProviderResult[] results = new ContentProviderResult[numOperations]; for (int i = 0; i < numOperations; i++) { results[i] = operations.get(i).apply(this, results, i); } db.setTransactionSuccessful(); return results; } finally { db.endTransaction(); } } /** * Build a simple {@link org.xbmc.android.util.google.SelectionBuilder} to match the requested * {@link android.net.Uri}. This is usually enough to support {@link #insert}, * {@link #update}, and {@link #delete} operations. */ private SelectionBuilder buildSimpleSelection(Uri uri) { final SelectionBuilder builder = new SelectionBuilder(); final int match = URI_MATCHER.match(uri); switch (match) { case MOVIES: { return builder.table(VideoDatabase.Tables.MOVIES).where(Movies.HOST_ID + "=?", getHostIdAsString()); } case MOVIES_ID: { final String movieId = Movies.getMovieId(uri); return builder.table(VideoDatabase.Tables.MOVIES).where(Movies.ID + "=?", movieId).where(Movies.HOST_ID + "=?", getHostIdAsString()); } case PEOPLE: { return builder.table(VideoDatabase.Tables.PEOPLE).where(People.HOST_ID + "=?", getHostIdAsString()); } case GENRES: { return builder.table(VideoDatabase.Tables.GENRES); } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } /** * Build an advanced {@link org.xbmc.android.util.google.SelectionBuilder} to match the requested * {@link android.net.Uri}. This is usually only used by {@link #query}, since it * performs table joins useful for {@link android.database.Cursor} data. */ private SelectionBuilder buildExpandedSelection(Uri uri, int match) { final SelectionBuilder builder = new SelectionBuilder(); switch (match) { case MOVIES: { return builder.table(VideoDatabase.Tables.MOVIES).where(Movies.HOST_ID + "=?", getHostIdAsString()); } case MOVIES_ID: { final String movieId = Movies.getMovieId(uri); return builder.table(VideoDatabase.Tables.MOVIES).where(Movies._ID + "=?", movieId); } case MOVIES_MOVIECAST: { final String movieId = MovieCast.getMovieId(uri); return builder.table(VideoDatabase.Tables.PEOPLE_MOVIE_CAST_JOIN_PEOPLE).where(MovieCast.MOVIE_REF + "=?", movieId); } case PEOPLE: { return builder.table(VideoDatabase.Tables.PEOPLE).where(People.HOST_ID + "=?", getHostIdAsString()); } case GENRES: { return builder.table(VideoDatabase.Tables.GENRES); } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } @Override public int bulkInsert(Uri uri, ContentValues[] values) { final SQLiteDatabase db = database.getWritableDatabase(); final int numInserted; db.beginTransaction(); try { numInserted = super.bulkInsert(uri, values); db.setTransactionSuccessful(); } finally { db.endTransaction(); getContext().getContentResolver().notifyChange(uri, null); } return numInserted; } }