/*
* Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.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.
*/
package org.devtcg.five.provider;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.devtcg.five.Constants;
import org.devtcg.five.provider.AbstractTableMerger.SyncableColumns;
import org.devtcg.five.provider.util.AlbumMerger;
import org.devtcg.five.provider.util.ArtistMerger;
import org.devtcg.five.provider.util.PlaylistMerger;
import org.devtcg.five.provider.util.PlaylistSongMerger;
import org.devtcg.five.provider.util.SongItem;
import org.devtcg.five.provider.util.SongMerger;
import org.devtcg.five.provider.util.SourceItem;
import org.devtcg.five.util.FileUtils;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils.InsertHelper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
public class FiveProvider extends AbstractSyncProvider
{
private static final String TAG = "FiveProvider";
/**
* Used by the sync provider instance to determine which source we are
* synchronizing.
*/
public SourceItem mSource;
DatabaseHelper mHelper;
private static final String DATABASE_NAME = "five.db";
private static final int DATABASE_VERSION = 36;
private static final UriMatcher sUriMatcher;
private static final HashMap<String, String> sArtistsMap;
private static final HashMap<String, String> sAlbumsMap;
private static final HashMap<String, String> sSongsMap;
private InsertHelper mArtistInserter;
private InsertHelper mAlbumInserter;
private InsertHelper mSongInserter;
private InsertHelper mDeletedArtistInserter;
private InsertHelper mDeletedAlbumInserter;
private InsertHelper mDeletedSongInserter;
private InsertHelper mDeletedPlaylistInserter;
private InsertHelper mDeletedPlaylistSongInserter;
private static enum URIPatternIds
{
SOURCES, SOURCE,
ARTISTS, ARTIST, ARTIST_PHOTO, DELETED_ARTIST,
ALBUMS, ALBUMS_BY_ARTIST, ALBUMS_WITH_ARTIST, ALBUMS_COMPLETE, ALBUM,
ALBUM_ARTWORK, ALBUM_ARTWORK_BIG, DELETED_ALBUM,
SONGS, SONGS_BY_ALBUM, SONGS_BY_ARTIST, SONGS_BY_ARTIST_ON_ALBUM, SONG,
DELETED_SONG,
PLAYLISTS, PLAYLIST, SONGS_IN_PLAYLIST, SONG_IN_PLAYLIST,
PLAYLIST_SONG, PLAYLIST_SONGS, DELETED_PLAYLIST, DELETED_PLAYLIST_SONG,
CACHE, CACHE_ITEMS_BY_SOURCE,
ADJUST_COUNTS,
;
public static URIPatternIds get(int ordinal)
{
return values()[ordinal];
}
}
private class DatabaseHelper extends SQLiteOpenHelper
{
public DatabaseHelper(Context ctx, String databaseName)
{
super(ctx, databaseName, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL(Five.Sources.SQL.CREATE);
execStatements(db, Five.Music.Artists.SQL.CREATE);
execStatements(db, Five.Music.Albums.SQL.CREATE);
execStatements(db, Five.Music.Songs.SQL.CREATE);
execStatements(db, Five.Music.Playlists.SQL.CREATE);
execStatements(db, Five.Music.PlaylistSongs.SQL.CREATE);
if (isTemporary() == false)
{
execStatements(db, Five.Music.Albums.SQL.INDEX);
execStatements(db, Five.Music.Songs.SQL.INDEX);
execStatements(db, Five.Music.PlaylistSongs.SQL.INDEX);
}
}
private void createDeletedTable(SQLiteDatabase db, String deletedTable)
{
db.execSQL("CREATE TABLE " + deletedTable + " (" +
SyncableColumns._ID + " INTEGER PRIMARY KEY, " +
SyncableColumns._SYNC_ID + " INTEGER, " +
SyncableColumns._SYNC_TIME + " BIGINT " +
")");
}
private void execStatements(SQLiteDatabase db, String[] statements)
{
for (int i = 0; i < statements.length; i++)
db.execSQL(statements[i]);
}
private void onDrop(SQLiteDatabase db)
{
db.execSQL(Five.Sources.SQL.DROP);
execStatements(db, Five.Music.Artists.SQL.DROP);
execStatements(db, Five.Music.Albums.SQL.DROP);
execStatements(db, Five.Music.Songs.SQL.DROP);
execStatements(db, Five.Music.Playlists.SQL.DROP);
execStatements(db, Five.Music.PlaylistSongs.SQL.DROP);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
if (oldVersion == 17 && newVersion == 18)
{
Log.w(TAG, "Attempting to upgrade to " + newVersion);
execStatements(db, Five.Music.Artists.SQL.INDEX);
execStatements(db, Five.Music.Albums.SQL.INDEX);
execStatements(db, Five.Music.Songs.SQL.INDEX);
}
else
{
Log.w(TAG, "Version too old, wiping out database contents...");
onDrop(db);
onCreate(db);
}
}
@Override
public void onOpen(SQLiteDatabase db)
{
mArtistInserter = new InsertHelper(db, Five.Music.Artists.SQL.TABLE);
mAlbumInserter = new InsertHelper(db, Five.Music.Albums.SQL.TABLE);
mSongInserter = new InsertHelper(db, Five.Music.Songs.SQL.TABLE);
mDeletedArtistInserter = new InsertHelper(db, Five.Music.Artists.SQL.DELETED_TABLE);
mDeletedAlbumInserter = new InsertHelper(db, Five.Music.Albums.SQL.DELETED_TABLE);
mDeletedSongInserter = new InsertHelper(db, Five.Music.Songs.SQL.DELETED_TABLE);
mDeletedPlaylistInserter = new InsertHelper(db, Five.Music.Playlists.SQL.DELETED_TABLE);
mDeletedPlaylistSongInserter = new InsertHelper(db, Five.Music.PlaylistSongs.SQL.DELETED_TABLE);
}
}
private static final AbstractSyncProvider.Creator<FiveProvider> CREATOR =
new AbstractSyncProvider.Creator<FiveProvider>()
{
@Override
public FiveProvider newInstance()
{
return new FiveProvider();
}
};
@Override
public AbstractSyncAdapter getSyncAdapter()
{
return new FiveSyncAdapter(getContext(), this);
}
@Override
public AbstractSyncProvider getSyncInstance()
{
String dbName = "_sync-" + mSource.getId();
File path = getContext().getDatabasePath(dbName);
FiveProvider provider = CREATOR.getSyncInstance(path);
provider.mSource = mSource;
provider.mHelper = provider.new DatabaseHelper(getContext(), dbName);
return provider;
}
@Override
public boolean onCreate()
{
if (isTemporary())
throw new IllegalStateException("onCreate should not be called on temp providers");
mHelper = new DatabaseHelper(getContext(), DATABASE_NAME);
return true;
}
@Override
public void close()
{
mHelper.close();
}
@Override
public SQLiteDatabase getDatabase()
{
return mHelper.getWritableDatabase();
}
@Override
protected Iterable<? extends AbstractTableMerger> getMergers()
{
ArrayList<AbstractTableMerger> list = new ArrayList<AbstractTableMerger>(3);
list.add(new ArtistMerger(this));
list.add(new AlbumMerger(this));
list.add(new SongMerger(this));
list.add(new PlaylistMerger(this));
list.add(new PlaylistSongMerger(this));
return list;
}
private static String getSecondToLastPathSegment(Uri uri)
{
List<String> segments = uri.getPathSegments();
int size;
if ((size = segments.size()) < 2)
throw new IllegalArgumentException("URI is not long enough to have a second-to-last path");
return segments.get(size - 2);
}
private static List<Long> getNumericPathSegments(Uri uri)
{
List<String> segments = uri.getPathSegments();
ArrayList<Long> numeric = new ArrayList<Long>(3);
for (String segment: segments)
{
try {
numeric.add(Long.parseLong(segment));
} catch (NumberFormatException e) {}
}
return numeric;
}
private static void checkWritePermission()
{
if (Binder.getCallingPid() != Process.myPid())
throw new SecurityException("Write access is not permitted.");
}
/*-***********************************************************************/
private static int stringModeToInt(Uri uri, String mode)
throws FileNotFoundException
{
int modeBits;
if ("r".equals(mode))
{
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
}
else if ("w".equals(mode) || "wt".equals(mode))
{
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
}
else if ("wa".equals(mode))
{
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
}
else if ("rw".equals(mode))
{
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
}
else if ("rwt".equals(mode))
{
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
}
else
{
throw new FileNotFoundException("Bad mode for " + uri + ": " + mode);
}
return modeBits;
}
public static File getArtistPhoto(long id, boolean temporary) throws FileNotFoundException
{
FileUtils.mkdirIfNecessary(Constants.sArtistPhotoDir);
if (temporary == true)
return new File(Constants.sArtistPhotoDir, id + ".tmp");
else
return new File(Constants.sArtistPhotoDir, String.valueOf(id));
}
public static File getAlbumArtwork(long id, boolean temporary) throws FileNotFoundException
{
return getAlbumArtwork(id, URIPatternIds.ALBUM_ARTWORK, temporary);
}
public static File getLargeAlbumArtwork(long id, boolean temporary) throws FileNotFoundException
{
return getAlbumArtwork(id, URIPatternIds.ALBUM_ARTWORK_BIG, temporary);
}
private static File getAlbumArtwork(long id, URIPatternIds type, boolean temporary)
throws FileNotFoundException
{
FileUtils.mkdirIfNecessary(Constants.sAlbumArtworkDir);
String filename;
if (type == URIPatternIds.ALBUM_ARTWORK)
filename = String.valueOf(id);
else
filename = id + "-big";
if (temporary == true)
return new File(Constants.sAlbumArtworkDir, filename + ".tmp");
else
return new File(Constants.sAlbumArtworkDir, filename);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException
{
File file;
URIPatternIds type = URIPatternIds.get(sUriMatcher.match(uri));
switch (type)
{
case ALBUM_ARTWORK:
case ALBUM_ARTWORK_BIG:
String albumId = uri.getPathSegments().get(3);
file = getAlbumArtwork(Long.parseLong(albumId), type, isTemporary());
return ParcelFileDescriptor.open(file, stringModeToInt(uri, mode));
case ARTIST_PHOTO:
String artistId = getSecondToLastPathSegment(uri);
file = getArtistPhoto(Long.parseLong(artistId), isTemporary());
return ParcelFileDescriptor.open(file, stringModeToInt(uri, mode));
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
@Override
public Cursor queryInternal(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String groupBy = null;
URIPatternIds type = URIPatternIds.get(sUriMatcher.match(uri));
switch (type)
{
case SOURCES:
qb.setTables(Five.Sources.SQL.TABLE);
break;
case SOURCE:
qb.setTables(Five.Sources.SQL.TABLE);
qb.appendWhere("_id=" + uri.getLastPathSegment());
break;
case PLAYLIST_SONGS:
qb.setTables(Five.Music.PlaylistSongs.SQL.TABLE);
break;
case PLAYLISTS:
qb.setTables(Five.Music.Playlists.SQL.TABLE);
break;
case PLAYLIST:
qb.setTables(Five.Music.Playlists.SQL.TABLE);
qb.appendWhere("_id=" + uri.getLastPathSegment());
break;
case SONGS_IN_PLAYLIST:
qb.setTables(Five.Music.PlaylistSongs.SQL.TABLE + " ps " +
"LEFT JOIN " + Five.Music.Songs.SQL.TABLE + " s " +
"ON s." + Five.Music.Songs._ID + " = ps." + Five.Music.PlaylistSongs.SONG_ID);
qb.appendWhere("ps.playlist_id=" + getSecondToLastPathSegment(uri));
qb.setProjectionMap(sSongsMap);
if (sortOrder == null)
sortOrder = "ps.position ASC";
break;
case SONGS:
qb.setTables(Five.Music.Songs.SQL.TABLE);
break;
case SONG:
qb.setTables(Five.Music.Songs.SQL.TABLE);
qb.appendWhere("_id=" + uri.getLastPathSegment());
break;
case SONGS_BY_ARTIST:
qb.setTables(Five.Music.Songs.SQL.TABLE);
qb.appendWhere("artist_id=" + getSecondToLastPathSegment(uri));
if (sortOrder == null)
sortOrder = "title ASC";
break;
case SONGS_BY_ALBUM:
case SONGS_BY_ARTIST_ON_ALBUM:
qb.setTables(Five.Music.Songs.SQL.TABLE);
if (type == URIPatternIds.SONGS_BY_ALBUM)
qb.appendWhere("album_id=" + getSecondToLastPathSegment(uri));
else /* if (type == URIPatternIds.SONGS_BY_ARTIST_ON_ALBUM) */
{
List<Long> segs = getNumericPathSegments(uri);
qb.appendWhere("artist_id=" + segs.get(0) +
" AND album_id=" + segs.get(1));
}
if (sortOrder == null)
sortOrder = "track_num ASC, title ASC";
break;
case ARTISTS:
case ARTIST:
qb.setTables(Five.Music.Artists.SQL.TABLE);
if (type == URIPatternIds.ARTIST)
qb.appendWhere("_id=" + uri.getLastPathSegment());
qb.setProjectionMap(sArtistsMap);
break;
case ALBUM:
case ALBUMS:
case ALBUMS_BY_ARTIST:
case ALBUMS_COMPLETE:
qb.setTables(Five.Music.Albums.SQL.TABLE + " a " +
"LEFT JOIN " + Five.Music.Artists.SQL.TABLE + " artists " +
"ON artists." + Five.Music.Artists._ID + " = a." + Five.Music.Albums.ARTIST_ID);
if (type == URIPatternIds.ALBUM)
qb.appendWhere("a._id=" + uri.getLastPathSegment());
else
{
if (type == URIPatternIds.ALBUMS_BY_ARTIST)
qb.appendWhere("a.artist_id=" + getSecondToLastPathSegment(uri));
else if (type == URIPatternIds.ALBUMS_COMPLETE)
qb.appendWhere("a.num_songs > 3");
}
qb.setProjectionMap(sAlbumsMap);
break;
case ALBUMS_WITH_ARTIST:
qb.setTables(Five.Music.Songs.SQL.TABLE + " s " +
"LEFT JOIN " + Five.Music.Albums.SQL.TABLE + " a " +
"ON a." + Five.Music.Albums._ID + " = s." + Five.Music.Songs.ALBUM_ID + " " +
"LEFT JOIN " + Five.Music.Artists.SQL.TABLE + " artists " +
"ON artists." + Five.Music.Artists._ID + " = a." + Five.Music.Albums.ARTIST_ID);
qb.appendWhere("s.artist_id=" + getSecondToLastPathSegment(uri));
HashMap<String, String> proj = new HashMap<String, String>(sAlbumsMap);
proj.put(Five.Music.Albums.NUM_SONGS, "COUNT(*) AS " + Five.Music.Albums.NUM_SONGS);
qb.setProjectionMap(proj);
groupBy = "a." + Five.Music.Albums._ID;
break;
case DELETED_ARTIST:
qb.setTables(Five.Music.Artists.SQL.DELETED_TABLE);
break;
case DELETED_ALBUM:
qb.setTables(Five.Music.Albums.SQL.DELETED_TABLE);
break;
case DELETED_SONG:
qb.setTables(Five.Music.Songs.SQL.DELETED_TABLE);
break;
case DELETED_PLAYLIST:
qb.setTables(Five.Music.Playlists.SQL.DELETED_TABLE);
break;
case DELETED_PLAYLIST_SONG:
qb.setTables(Five.Music.PlaylistSongs.SQL.DELETED_TABLE);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
if (isTemporary() == false)
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/*-***********************************************************************/
private int updateSong(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v,
String sel, String[] selArgs)
{
String custom;
switch (type)
{
case SONG:
custom = extendWhere(sel, Five.Music.Songs._ID + '=' + uri.getLastPathSegment());
break;
case SONGS:
custom = sel;
break;
default:
throw new IllegalArgumentException();
}
int ret = db.update(Five.Music.Songs.SQL.TABLE, v, custom, selArgs);
return ret;
}
private int updateAlbum(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v,
String sel, String[] selArgs)
{
String custom;
custom = extendWhere(sel, Five.Music.Albums._ID + '=' + uri.getLastPathSegment());
int ret = db.update(Five.Music.Albums.SQL.TABLE, v, custom, selArgs);
return ret;
}
private int updateArtist(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v,
String sel, String[] selArgs)
{
String custom;
custom = extendWhere(sel, Five.Music.Artists._ID + '=' + uri.getLastPathSegment());
int ret = db.update(Five.Music.Artists.SQL.TABLE, v, custom, selArgs);
return ret;
}
private int updatePlaylist(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v,
String sel, String[] selArgs)
{
String custom;
custom = extendWhere(sel, Five.Music.Playlists._ID + '=' + uri.getLastPathSegment());
int ret = db.update(Five.Music.Playlists.SQL.TABLE, v, custom, selArgs);
return ret;
}
private int updateSource(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v,
String sel, String[] selArgs)
{
String custom;
custom = extendWhere(sel, Five.Sources._ID + '=' + uri.getLastPathSegment());
int ret = db.update(Five.Sources.SQL.TABLE, v, custom, selArgs);
if (isTemporary() == false)
getContext().getContentResolver().notifyChange(Five.Sources.CONTENT_URI, null);
return ret;
}
private void updateCount(SQLiteDatabase db, String updateSQL,
String countsSQL)
{
db.beginTransaction();
SQLiteStatement updateStmt = null;
try {
updateStmt = db.compileStatement(updateSQL);
Cursor counts = db.rawQuery(countsSQL, null);
try {
while (counts.moveToNext() == true)
{
long _id = counts.getLong(0);
long count = counts.getLong(1);
updateStmt.bindLong(1, count);
updateStmt.bindLong(2, _id);
updateStmt.execute();
}
} finally {
counts.close();
}
db.setTransactionSuccessful();
} finally {
if (updateStmt != null)
updateStmt.close();
db.endTransaction();
}
}
private int updateCounts(SQLiteDatabase db, Uri uri, URIPatternIds type,
ContentValues v, String sel, String[] args)
{
db.beginTransaction();
try {
updateCount(db, "UPDATE music_artists SET num_songs = ? WHERE _id = ?",
"SELECT artist_id, COUNT(*) FROM music_songs GROUP BY artist_id");
updateCount(db, "UPDATE music_artists SET num_albums = ? WHERE _id = ?",
"SELECT artist_id, COUNT(*) FROM (SELECT artist_id FROM music_songs GROUP BY artist_id, album_id) GROUP BY artist_id");
updateCount(db, "UPDATE music_albums SET num_songs = ? WHERE _id = ?",
"SELECT album_id, COUNT(*) FROM music_songs GROUP BY album_id");
updateCount(db, "UPDATE music_playlists SET num_songs = ? WHERE _id = ?",
"SELECT playlist_id, COUNT(*) FROM music_playlist_songs GROUP BY playlist_id");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return 1;
}
@Override
public int updateInternal(Uri uri, ContentValues values, String selection,
String[] selectionArgs)
{
checkWritePermission();
SQLiteDatabase db = mHelper.getWritableDatabase();
URIPatternIds type = URIPatternIds.get(sUriMatcher.match(uri));
switch (type)
{
case SONG:
case SONGS:
return updateSong(db, uri, type, values, selection, selectionArgs);
case ALBUM:
return updateAlbum(db, uri, type, values, selection, selectionArgs);
case ARTIST:
return updateArtist(db, uri, type, values, selection, selectionArgs);
case PLAYLIST:
return updatePlaylist(db, uri, type, values, selection, selectionArgs);
case SOURCE:
return updateSource(db, uri, type, values, selection, selectionArgs);
case ADJUST_COUNTS:
return updateCounts(db, uri, type, values, selection, selectionArgs);
default:
throw new IllegalArgumentException("Cannot update URI: " + uri);
}
}
/*-***********************************************************************/
private Uri insertSource(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
if (v.containsKey(Five.Sources.HOST) == false)
throw new IllegalArgumentException("HOST cannot be NULL");
if (v.containsKey(Five.Sources.PORT) == false)
throw new IllegalArgumentException("PORT cannot be NULL");
if (v.containsKey(Five.Sources.LAST_SYNC_TIME) == false)
v.put(Five.Sources.LAST_SYNC_TIME, 0);
long id = db.insert(Five.Sources.SQL.TABLE, Five.Sources.HOST, v);
if (id == -1)
return null;
Uri ret = ContentUris.withAppendedId(Five.Sources.CONTENT_URI, id);
if (isTemporary() == false)
getContext().getContentResolver().notifyChange(Five.Sources.CONTENT_URI, null);
return ret;
}
private boolean adjustNameWithPrefix(ContentValues v)
{
String name = v.getAsString(Five.Music.Artists.NAME);
if (name.startsWith("The ") == true)
{
v.put(Five.Music.Artists.NAME, name.substring(4));
v.put(Five.Music.Artists.NAME_PREFIX, "The ");
return true;
}
return false;
}
private Uri insertArtist(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
if (v.containsKey(Five.Music.Artists.NAME) == false)
throw new IllegalArgumentException("NAME cannot be NULL");
if (v.containsKey(Five.Music.Artists.NUM_ALBUMS) == false)
v.put(Five.Music.Artists.NUM_ALBUMS, 0);
if (v.containsKey(Five.Music.Artists.NUM_SONGS) == false)
v.put(Five.Music.Artists.NUM_SONGS, 0);
adjustNameWithPrefix(v);
long id = mArtistInserter.insert(v);
if (id == -1)
return null;
Uri ret = ContentUris.withAppendedId(Five.Music.Artists.CONTENT_URI, id);
return ret;
}
private Uri insertAlbum(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
if (v.containsKey(Five.Music.Albums.NAME) == false)
throw new IllegalArgumentException("NAME cannot be NULL");
if (v.containsKey(Five.Music.Albums.ARTIST_ID) == false)
throw new IllegalArgumentException("ARTIST_ID cannot be NULL");
if (v.containsKey(Five.Music.Albums.NUM_SONGS) == false)
v.put(Five.Music.Albums.NUM_SONGS, 0);
adjustNameWithPrefix(v);
long id = mAlbumInserter.insert(v);
if (id == -1)
return null;
Uri ret = ContentUris.withAppendedId(Five.Music.Albums.CONTENT_URI, id);
return ret;
}
private Uri insertSong(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
if (v.containsKey(Five.Music.Albums.ARTIST_ID) == false)
throw new IllegalArgumentException("ARTIST_ID cannot be NULL");
long id = mSongInserter.insert(v);
if (id == -1)
return null;
Uri ret = ContentUris.withAppendedId(Five.Music.Songs.CONTENT_URI, id);
return ret;
}
private Uri insertPlaylist(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
if (v.containsKey(Five.Music.Playlists.NAME) == false)
throw new IllegalArgumentException("NAME cannot be NULL");
if (v.containsKey(Five.Music.Playlists.NUM_SONGS) == false)
v.put(Five.Music.Playlists.NUM_SONGS, 0);
long id = db.insert(Five.Music.Playlists.SQL.TABLE,
Five.Music.Playlists.NAME, v);
if (id == -1)
return null;
Uri playlistUri = ContentUris
.withAppendedId(Five.Music.Playlists.CONTENT_URI, id);
return playlistUri;
}
private Uri insertPlaylistSongs(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
/* TODO: Maybe lack of POSITION means append? */
if (v.containsKey(Five.Music.PlaylistSongs.POSITION) == false)
throw new IllegalArgumentException("POSITION cannot be NULL");
if (v.containsKey(Five.Music.PlaylistSongs.SONG_ID) == false)
throw new IllegalArgumentException("SONG_ID cannot be NULL");
if (type == URIPatternIds.SONGS_IN_PLAYLIST)
{
v.put(Five.Music.PlaylistSongs.PLAYLIST_ID,
getSecondToLastPathSegment(uri));
}
if (v.containsKey(Five.Music.PlaylistSongs.PLAYLIST_ID) == false)
throw new IllegalArgumentException("PLAYLIST_ID cannot be NULL");
/* TODO: Check that the inserted POSITION doesn't require that we
* reposition other songs. */
db.insert(Five.Music.PlaylistSongs.SQL.TABLE,
Five.Music.PlaylistSongs.PLAYLIST_ID, v);
Uri playlistSongUri = uri.buildUpon()
.appendEncodedPath(v.getAsString(Five.Music.PlaylistSongs.POSITION))
.build();
return playlistSongUri;
}
private Uri insertDeletedItem(SQLiteDatabase db, Uri uri, URIPatternIds type, ContentValues v)
{
InsertHelper inserter;
switch (type) {
case DELETED_ARTIST: inserter = mDeletedArtistInserter; break;
case DELETED_ALBUM: inserter = mDeletedAlbumInserter; break;
case DELETED_SONG: inserter = mDeletedSongInserter; break;
case DELETED_PLAYLIST: inserter = mDeletedPlaylistInserter; break;
case DELETED_PLAYLIST_SONG: inserter = mDeletedPlaylistSongInserter; break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
long id = inserter.insert(v);
if (id == -1)
return null;
return ContentUris.withAppendedId(uri, id);
}
@Override
public Uri insertInternal(Uri uri, ContentValues values)
{
checkWritePermission();
SQLiteDatabase db = mHelper.getWritableDatabase();
URIPatternIds type = URIPatternIds.get(sUriMatcher.match(uri));
switch (type)
{
case SOURCES:
return insertSource(db, uri, type, values);
case ARTISTS:
return insertArtist(db, uri, type, values);
case ALBUMS:
return insertAlbum(db, uri, type, values);
case SONGS:
return insertSong(db, uri, type, values);
case PLAYLISTS:
return insertPlaylist(db, uri, type, values);
case SONGS_IN_PLAYLIST:
case PLAYLIST_SONGS:
return insertPlaylistSongs(db, uri, type, values);
case DELETED_ARTIST:
case DELETED_ALBUM:
case DELETED_SONG:
case DELETED_PLAYLIST:
case DELETED_PLAYLIST_SONG:
return insertDeletedItem(db, uri, type, values);
}
throw new IllegalArgumentException("Cannot insert URI: " + uri);
}
/*-***********************************************************************/
private static String extendWhere(String old, String[] add)
{
StringBuilder ret = new StringBuilder();
int length = add.length;
if (length > 0)
{
ret.append("(" + add[0] + ")");
for (int i = 1; i < length; i++)
{
ret.append(" AND (");
ret.append(add[i]);
ret.append(')');
}
}
if (TextUtils.isEmpty(old) == false)
ret.append(" AND (").append(old).append(')');
return ret.toString();
}
private static String extendWhere(String old, String add)
{
return extendWhere(old, new String[] { add });
}
private int deleteSources(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
String custom;
int count;
switch (type)
{
case SOURCES:
custom = selection;
break;
case SOURCE:
StringBuilder where = new StringBuilder();
where.append(Five.Sources._ID).append('=').append(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection) == false)
where.append(" AND (").append(selection).append(')');
custom = where.toString();
break;
default:
throw new IllegalArgumentException("Cannot delete source URI: " + uri);
}
count = db.delete(Five.Sources.SQL.TABLE, custom, selectionArgs);
if (isTemporary() == false)
getContext().getContentResolver().notifyChange(Five.Sources.CONTENT_URI, null);
return count;
}
private void assertNoSelection(String selection, String[] selectionArgs)
{
if (selection != null || selectionArgs != null)
throw new IllegalArgumentException();
}
private int deleteArtist(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
assertNoSelection(selection, selectionArgs);
long artistId = ContentUris.parseId(uri);
int count = db.delete(Five.Music.Artists.SQL.TABLE,
Five.Music.Artists._ID + " = " + artistId, null);
try {
if (count > 0)
getArtistPhoto(artistId, false).delete();
} catch (FileNotFoundException e) {
if (Constants.DEBUG)
Log.d(TAG, "Unexpected sdcard error: " + e.toString());
}
return count;
}
private int deleteAlbum(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
assertNoSelection(selection, selectionArgs);
long albumId = ContentUris.parseId(uri);
int count = db.delete(Five.Music.Albums.SQL.TABLE,
Five.Music.Albums._ID + " = " + albumId, null);
try {
if (count > 0)
{
getAlbumArtwork(albumId, false).delete();
getLargeAlbumArtwork(albumId, false).delete();
}
} catch (FileNotFoundException e) {
if (Constants.DEBUG)
Log.d(TAG, "Unexpected sdcard error: " + e.toString());
}
return count;
}
private int deleteSong(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
assertNoSelection(selection, selectionArgs);
long songId = ContentUris.parseId(uri);
String queryForSongId = Five.Music.Songs._ID + " = " + songId;
SongItem item = SongItem.getInstance(db.query(Five.Music.Songs.SQL.TABLE, null,
queryForSongId, null, null, null, null));
String cachePath = null;
if (item != null)
{
try {
cachePath = item.getCachePath();
} finally {
item.close();
}
}
int count = db.delete(Five.Music.Songs.SQL.TABLE, queryForSongId, null);
if (count > 0)
new File(cachePath).delete();
return count;
}
private int deletePlaylist(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
assertNoSelection(selection, selectionArgs);
return db.delete(Five.Music.Playlists.SQL.TABLE,
Five.Music.Playlists._ID + " = " + ContentUris.parseId(uri), null);
}
private int deletePlaylistSong(SQLiteDatabase db, Uri uri, URIPatternIds type,
String selection, String[] selectionArgs)
{
assertNoSelection(selection, selectionArgs);
return db.delete(Five.Music.PlaylistSongs.SQL.TABLE,
Five.Music.PlaylistSongs._ID + " = " + ContentUris.parseId(uri), null);
}
@Override
public int deleteInternal(Uri uri, String selection, String[] selectionArgs)
{
checkWritePermission();
SQLiteDatabase db = mHelper.getWritableDatabase();
URIPatternIds type = URIPatternIds.get(sUriMatcher.match(uri));
switch (type)
{
case SOURCES:
case SOURCE:
return deleteSources(db, uri, type, selection, selectionArgs);
case ARTIST:
return deleteArtist(db, uri, type, selection, selectionArgs);
case ALBUM:
return deleteAlbum(db, uri, type, selection, selectionArgs);
case SONG:
return deleteSong(db, uri, type, selection, selectionArgs);
case PLAYLIST:
return deletePlaylist(db, uri, type, selection, selectionArgs);
case PLAYLIST_SONG:
return deletePlaylistSong(db, uri, type, selection, selectionArgs);
default:
throw new IllegalArgumentException("Cannot delete URI: " + uri);
}
}
/*-***********************************************************************/
@Override
public String getType(Uri uri)
{
switch (URIPatternIds.get(sUriMatcher.match(uri)))
{
case SOURCES:
return Five.Sources.CONTENT_TYPE;
case SOURCE:
return Five.Sources.CONTENT_ITEM_TYPE;
case ARTISTS:
return Five.Music.Artists.CONTENT_TYPE;
case ARTIST:
return Five.Music.Artists.CONTENT_ITEM_TYPE;
case ALBUMS:
case ALBUMS_BY_ARTIST:
return Five.Music.Albums.CONTENT_TYPE;
case ALBUM:
return Five.Music.Albums.CONTENT_ITEM_TYPE;
case SONGS:
case SONGS_BY_ALBUM:
case SONGS_BY_ARTIST:
return Five.Music.Songs.CONTENT_TYPE;
case SONG:
return Five.Music.Songs.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
/*-***********************************************************************/
static
{
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(Five.AUTHORITY, "sources", URIPatternIds.SOURCES.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "sources/#", URIPatternIds.SOURCE.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists", URIPatternIds.ARTISTS.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/#", URIPatternIds.ARTIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/#/albums", URIPatternIds.ALBUMS_WITH_ARTIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/#/albums/#/songs", URIPatternIds.SONGS_BY_ARTIST_ON_ALBUM.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/#/songs", URIPatternIds.SONGS_BY_ARTIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/#/photo", URIPatternIds.ARTIST_PHOTO.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/artists/deleted", URIPatternIds.DELETED_ARTIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums", URIPatternIds.ALBUMS.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/complete", URIPatternIds.ALBUMS_COMPLETE.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/#", URIPatternIds.ALBUM.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/#/songs", URIPatternIds.SONGS_BY_ALBUM.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/#/artwork", URIPatternIds.ALBUM_ARTWORK.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/#/artwork/big", URIPatternIds.ALBUM_ARTWORK_BIG.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/albums/deleted", URIPatternIds.DELETED_ALBUM.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/songs", URIPatternIds.SONGS.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/songs/#", URIPatternIds.SONG.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/songs/deleted", URIPatternIds.DELETED_SONG.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists", URIPatternIds.PLAYLISTS.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/#", URIPatternIds.PLAYLIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/deleted", URIPatternIds.DELETED_PLAYLIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/#/songs", URIPatternIds.SONGS_IN_PLAYLIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/#/song/#", URIPatternIds.SONG_IN_PLAYLIST.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/songs", URIPatternIds.PLAYLIST_SONGS.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/songs/#", URIPatternIds.PLAYLIST_SONG.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/playlists/songs/deleted", URIPatternIds.DELETED_PLAYLIST_SONG.ordinal());
sUriMatcher.addURI(Five.AUTHORITY, "media/music/adjust_counts", URIPatternIds.ADJUST_COUNTS.ordinal());
sArtistsMap = new HashMap<String, String>();
sArtistsMap.put(Five.Music.Artists.MBID, Five.Music.Artists.MBID);
sArtistsMap.put(Five.Music.Artists._ID, Five.Music.Artists._ID);
sArtistsMap.put(Five.Music.Artists._SYNC_ID, Five.Music.Artists._SYNC_ID);
sArtistsMap.put(Five.Music.Artists._SYNC_TIME, Five.Music.Artists._SYNC_TIME);
sArtistsMap.put(Five.Music.Artists.DISCOVERY_DATE, Five.Music.Artists.DISCOVERY_DATE);
sArtistsMap.put(Five.Music.Artists.GENRE, Five.Music.Artists.GENRE);
sArtistsMap.put(Five.Music.Artists.NAME, Five.Music.Artists.NAME);
sArtistsMap.put(Five.Music.Artists.NAME_PREFIX, Five.Music.Artists.NAME_PREFIX);
sArtistsMap.put(Five.Music.Artists.FULL_NAME, "IFNULL(" + Five.Music.Artists.NAME_PREFIX + ", \"\") || " + Five.Music.Artists.NAME + " AS " + Five.Music.Artists.FULL_NAME);
sArtistsMap.put(Five.Music.Artists.PHOTO, Five.Music.Artists.PHOTO);
sArtistsMap.put(Five.Music.Artists.NUM_ALBUMS, Five.Music.Artists.NUM_ALBUMS);
sArtistsMap.put(Five.Music.Artists.NUM_SONGS, Five.Music.Artists.NUM_SONGS);
sAlbumsMap = new HashMap<String, String>();
sAlbumsMap.put(Five.Music.Albums._ID, "a." + Five.Music.Albums._ID + " AS " + Five.Music.Albums._ID);
sAlbumsMap.put(Five.Music.Albums._SYNC_ID, "a." + Five.Music.Albums._SYNC_ID + " AS " + Five.Music.Albums._SYNC_ID);
sAlbumsMap.put(Five.Music.Albums._SYNC_TIME, "a." + Five.Music.Albums._SYNC_TIME + " AS " + Five.Music.Albums._SYNC_TIME);
sAlbumsMap.put(Five.Music.Albums.MBID, "a." + Five.Music.Albums.MBID + " AS " + Five.Music.Albums.MBID);
sAlbumsMap.put(Five.Music.Albums.ARTIST_ID, "a." + Five.Music.Albums.ARTIST_ID + " AS " + Five.Music.Albums.ARTIST_ID);
sAlbumsMap.put(Five.Music.Albums.ARTIST, "artists." + Five.Music.Artists.NAME + " AS " + Five.Music.Albums.ARTIST);
sAlbumsMap.put(Five.Music.Albums.ARTWORK, "a." + Five.Music.Albums.ARTWORK + " AS " + Five.Music.Albums.ARTWORK);
sAlbumsMap.put(Five.Music.Albums.ARTWORK_BIG, "a." + Five.Music.Albums.ARTWORK_BIG + " AS " + Five.Music.Albums.ARTWORK_BIG);
sAlbumsMap.put(Five.Music.Albums.DISCOVERY_DATE, "a." + Five.Music.Albums.DISCOVERY_DATE + " AS " + Five.Music.Albums.DISCOVERY_DATE);
sAlbumsMap.put(Five.Music.Albums.NAME, "a." + Five.Music.Albums.NAME + " AS " + Five.Music.Albums.NAME);
sAlbumsMap.put(Five.Music.Albums.NAME_PREFIX, "a." + Five.Music.Albums.NAME_PREFIX + " AS " + Five.Music.Albums.NAME_PREFIX);
sAlbumsMap.put(Five.Music.Albums.FULL_NAME, "IFNULL(a." + Five.Music.Albums.NAME_PREFIX + ", \"\") || a." + Five.Music.Albums.NAME + " AS " + Five.Music.Albums.FULL_NAME);
sAlbumsMap.put(Five.Music.Albums.RELEASE_DATE, "a." + Five.Music.Albums.RELEASE_DATE + " AS " + Five.Music.Albums.RELEASE_DATE);
sAlbumsMap.put(Five.Music.Albums.NUM_SONGS, "a." + Five.Music.Albums.NUM_SONGS + " AS " + Five.Music.Albums.NUM_SONGS);
sSongsMap = new HashMap<String, String>();
sSongsMap.put(Five.Music.Songs._ID, "s." + Five.Music.Songs._ID + " AS " + Five.Music.Songs._ID);
sSongsMap.put(Five.Music.Songs._SYNC_ID, "s." + Five.Music.Songs._SYNC_ID + " AS " + Five.Music.Songs._SYNC_ID);
sSongsMap.put(Five.Music.Songs._SYNC_TIME, "s." + Five.Music.Songs._SYNC_TIME + " AS " + Five.Music.Songs._SYNC_TIME);
sSongsMap.put(Five.Music.Songs.MBID, "s." + Five.Music.Songs.MBID + " AS " + Five.Music.Songs.MBID);
sSongsMap.put(Five.Music.Songs.TITLE, "s." + Five.Music.Songs.TITLE + " AS " + Five.Music.Songs.TITLE);
sSongsMap.put(Five.Music.Songs.ALBUM, "s." + Five.Music.Songs.ALBUM + " AS " + Five.Music.Songs.ALBUM);
sSongsMap.put(Five.Music.Songs.ALBUM_ID, "s." + Five.Music.Songs.ALBUM_ID + " AS " + Five.Music.Songs.ALBUM_ID);
sSongsMap.put(Five.Music.Songs.ARTIST, "s." + Five.Music.Songs.ARTIST + " AS " + Five.Music.Songs.ARTIST);
sSongsMap.put(Five.Music.Songs.ARTIST_ID, "s." + Five.Music.Songs.ARTIST_ID + " AS " + Five.Music.Songs.ARTIST_ID);
sSongsMap.put(Five.Music.Songs.LENGTH, "s." + Five.Music.Songs.LENGTH + " AS " + Five.Music.Songs.LENGTH);
sSongsMap.put(Five.Music.Songs.TRACK, "s." + Five.Music.Songs.TRACK + " AS " + Five.Music.Songs.TRACK);
sSongsMap.put(Five.Music.Songs.SET, "s." + Five.Music.Songs.SET + " AS " + Five.Music.Songs.SET);
sSongsMap.put(Five.Music.Songs.GENRE, "s." + Five.Music.Songs.GENRE + " AS " + Five.Music.Songs.GENRE);
sSongsMap.put(Five.Music.Songs.DISCOVERY_DATE, "s." + Five.Music.Songs.DISCOVERY_DATE + " AS " + Five.Music.Songs.DISCOVERY_DATE);
}
}