/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox 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 3 of the License, or any later version. Jukefox 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 * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.jukefox.data.db; import java.io.File; import java.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.Vector; import ch.ethz.dcg.jukefox.commons.AbstractLanguageHelper; import ch.ethz.dcg.jukefox.commons.Constants; import ch.ethz.dcg.jukefox.commons.DataUnavailableException; import ch.ethz.dcg.jukefox.commons.DataWriteException; import ch.ethz.dcg.jukefox.commons.utils.Log; import ch.ethz.dcg.jukefox.commons.utils.Pair; import ch.ethz.dcg.jukefox.commons.utils.RandomProvider; import ch.ethz.dcg.jukefox.commons.utils.Utils; import ch.ethz.dcg.jukefox.commons.utils.kdtree.KdTreePoint; import ch.ethz.dcg.jukefox.data.cache.PreloadedSongInfo; import ch.ethz.dcg.jukefox.data.context.AbstractContextResult; import ch.ethz.dcg.jukefox.data.db.LockHelper.Lock; import ch.ethz.dcg.jukefox.data.db.LockHelper.LockType; import ch.ethz.dcg.jukefox.data.db.TransactionHelper.TransactionType; import ch.ethz.dcg.jukefox.manager.DirectoryManager; import ch.ethz.dcg.jukefox.manager.libraryimport.AbstractAlbumCoverFetcherThread.AlbumFetcherResult; import ch.ethz.dcg.jukefox.model.collection.AlbumStatus; import ch.ethz.dcg.jukefox.model.collection.BaseAlbum; import ch.ethz.dcg.jukefox.model.collection.BaseArtist; import ch.ethz.dcg.jukefox.model.collection.BaseSong; import ch.ethz.dcg.jukefox.model.collection.CompleteAlbum; import ch.ethz.dcg.jukefox.model.collection.CompleteArtist; import ch.ethz.dcg.jukefox.model.collection.CompleteTag; import ch.ethz.dcg.jukefox.model.collection.Genre; import ch.ethz.dcg.jukefox.model.collection.ListAlbum; import ch.ethz.dcg.jukefox.model.collection.MapAlbum; import ch.ethz.dcg.jukefox.model.collection.MapTag; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong.SongSource; import ch.ethz.dcg.jukefox.model.collection.SongCoords; import ch.ethz.dcg.jukefox.model.collection.SongStatus; import ch.ethz.dcg.jukefox.model.collection.statistics.CollectionProperties; import ch.ethz.dcg.jukefox.model.libraryimport.ContentProviderId; import ch.ethz.dcg.jukefox.model.libraryimport.GenreSongMap; import ch.ethz.dcg.jukefox.model.libraryimport.GenreSongMap.GenreSongEntry; import ch.ethz.dcg.jukefox.model.libraryimport.ImportAlbum; import ch.ethz.dcg.jukefox.model.libraryimport.ImportSong; import ch.ethz.dcg.jukefox.model.libraryimport.WebDataSong; import ch.ethz.dcg.jukefox.model.player.playlog.PlayLogEntry; import ch.ethz.dcg.jukefox.model.player.playlog.PlayLogSendEntity; import ch.ethz.dcg.jukefox.playmode.smartshuffle.NextSongCalculationThread; /** * Abstract for all SQLDatabase implementations */ public abstract class SqlDbDataPortal<ContentValues extends IContentValues> implements IDbDataPortal { private final static String TAG = SqlDbDataPortal.class.getSimpleName(); protected final DirectoryManager directoryManager; protected final AbstractLanguageHelper languageHelper; protected final String DB_URL; protected final String DB_USERNAME; protected final String DB_PASSWORD; private final ISqlDbConnection defaultConnection; private final ISqlDbConnection transactionConnection; private final LockHelper lockHelper; private final TransactionHelper transactionHelper; private final IDbLogHelper logDbHelper; private final IDbStatisticsHelper statisticsDbHelper; protected final static String NAMESPACE_COLLECTION_PROPERTIES = "statistics.collection_properties"; protected final static String CP_AVG_SONG_DISTANCE = "average_song_distance"; protected final static String CP_SONG_DISTANCE_STD_DEVIATION = "song_distance_standard_deviation"; /** * This class maintains a set of ContentValues suited to be inserted in a batch operation at once. All ContentValues * within an instance have to have exactly the same fields set. The list of fields is given in the constructor and * then the put-methods of the instance have to be used to set the value of each field given in the constructor. * Then calling {@link #saveContentValues()} commits the current values to the permanent list. * * @see SqlDbDataPortal#updateBatch(String, BatchContentValues, BatchContentValues) * @see SqlDbDataPortal#insertBatch(String, BatchContentValues, boolean) * * @author langnert */ protected class BatchContentValues { private Set<String> fieldNames; private List<ContentValues> contentValues; private ContentValues currentContentValues; public BatchContentValues(String[] fieldNames) { this(new HashSet<String>(Arrays.asList(fieldNames))); } public BatchContentValues(Set<String> fieldNames) { this.fieldNames = fieldNames; this.contentValues = new LinkedList<ContentValues>(); this.currentContentValues = createContentValues(); } /** * Validates whether the all fields of this instance have been filled with values and then saves the current * values permanently. */ public void saveContentValues() { if (currentContentValues.size() != fieldNames.size()) { throw new IllegalStateException( "Size of the ContentValues to be saved does not match field count."); } // validate all keys for (ContentValue cv : currentContentValues) { if (!fieldNames.contains(cv.getKey())) { throw new IllegalArgumentException( "The field " + cv.getKey() + " is not valid for this BatchContentValues."); } } // validation ok, so add it contentValues.add(this.currentContentValues); this.currentContentValues = createContentValues(); } public void put(String key, String value) { currentContentValues.put(key, value); } public void put(String key, Float value) { currentContentValues.put(key, value); } public void put(String key, Integer value) { currentContentValues.put(key, value); } public void put(String key, Double value) { currentContentValues.put(key, value); } public void put(String key, Boolean value) { currentContentValues.put(key, value); } public void put(String key, Long value) { currentContentValues.put(key, value); } public void addContentValues(ContentValues cvs) { this.currentContentValues = cvs; saveContentValues(); } public List<ContentValues> getContentValues() { return contentValues; } @Override public String toString() { return getContentValues().toString(); } public Set<String> getFieldNames() { return new HashSet<String>(fieldNames); } public int size() { return getContentValues().size(); } } /** * Just a helper to represent db connections. */ public interface ISqlDbConnection { /** * @see #open() */ public boolean open(); /** * @see IDbDataPortal#isOpen() */ public boolean isOpen(); /** * @see IDbDataPortal#close() */ public void close(); } // ---------------------------------------------------------------------------------------- // CONSTRUCTORS // ---------------------------------------------------------------------------------------- public SqlDbDataPortal(DirectoryManager directoryManager, AbstractLanguageHelper languageHelper) { this(directoryManager, languageHelper, directoryManager.getDataBaseConnectionString(), null, null); } public SqlDbDataPortal(DirectoryManager directoryManager, AbstractLanguageHelper languageHelper, String dbUrl) { this(directoryManager, languageHelper, dbUrl, null, null); } public SqlDbDataPortal(DirectoryManager directoryManager, AbstractLanguageHelper languageHelper, String dbUrl, String user, String password) { this.languageHelper = languageHelper; this.directoryManager = directoryManager; lockHelper = new LockHelper(this); transactionHelper = new TransactionHelper(this); logDbHelper = createLogDbHelper(); statisticsDbHelper = createStatisticsDbHelper(); DB_URL = dbUrl; DB_USERNAME = user; DB_PASSWORD = password; init(); defaultConnection = createDbConnection(); transactionConnection = createDbConnection(); open(); } /** * Called before the database is opened the first time. Initialize your environment in here, since the database will * typically be opened in the constructor (and therefore before your initialization phase in yours). */ protected abstract void init(); /** * Returns a new {@link IDbLogHelper}-instance. If you want to extend {@link DbLogHelper} then overwrite this method * and return the extended class instance. * * @return The instance */ protected IDbLogHelper createLogDbHelper() { return new DbLogHelper<ContentValues>(this); } /** * Returns a new {@link IDbStatisticsHelper}-instance. If you want to extend {@link DbStatisticsHelper} then * overwrite this method and return the extended class instance. * * @return The instance */ protected IDbStatisticsHelper createStatisticsDbHelper() { return new DbStatisticsHelper<ContentValues>(this); } @Override public final IDbLogHelper getLogHelper() { return logDbHelper; } @Override public final IDbStatisticsHelper getStatisticsHelper() { return statisticsDbHelper; } // ---------------------------------------------------------------------------------------- // LOCK HELPER // ---------------------------------------------------------------------------------------- /** * @see #lockS(ISqlDbConnection) */ public final Lock lockS() { return lockS(getConnection()); } /** * Acquires a shared lock. * * @see LockHelper * @see LockHelper#lock(LockType, ISqlDbConnection) */ public final Lock lockS(ISqlDbConnection connection) { return lockHelper.lock(LockType.SHARED, connection); } /** * @see #lockR(ISqlDbConnection) */ public final Lock lockR() { return lockR(getConnection()); } /** * Acquires a reserved lock. * * @see LockHelper * @see LockHelper#lock(LockType, ISqlDbConnection) */ public final Lock lockR(ISqlDbConnection connection) { return lockHelper.lock(LockType.RESERVED, connection); } /** * @see #lockX(ISqlDbConnection) */ public final Lock lockX() { return lockX(getConnection()); } /** * Acquires a exclusive lock. * * @see LockHelper * @see LockHelper#lock(LockType, ISqlDbConnection) */ public final Lock lockX(ISqlDbConnection connection) { return lockHelper.lock(LockType.EXCLUSIVE, connection); } // ---------------------------------------------------------------------------------------- // TRANSACTION HELPER // ---------------------------------------------------------------------------------------- @Override public final void beginTransaction() { transactionHelper.beginTransaction(); } @Override public final void beginExclusiveTransaction() { transactionHelper.beginExclusiveTransaction(); } @Override public final boolean inTransaction() { return transactionHelper.inTransaction(); } /** * @see TransactionHelper#getTransactionType() */ public final TransactionType getTransactionType() { return transactionHelper.getTransactionType(); } @Override public final void setTransactionSuccessful() { transactionHelper.setTransactionSuccessful(); } @Override public final void endTransaction() { transactionHelper.endTransaction(); } // ---------------------------------------------------------------------------------------- // CONNECTION // ---------------------------------------------------------------------------------------- protected abstract ISqlDbConnection createDbConnection(); /** * Returns the appropriate connection. If the current thread is in a transaction, we return the transaction * connection. Otherwise the default connection gets returned. * * @return The connection to the database */ public ISqlDbConnection getConnection() { if (inTransaction()) { return getTransactionConnection(); } else { return getDefaultConnection(); } } public ISqlDbConnection getDefaultConnection() { return defaultConnection; } public ISqlDbConnection getTransactionConnection() { return transactionConnection; } /** * Opens the connection to the database. * * @return True, if successful. */ protected final boolean open() { Log.v(TAG, "defaultConnection: " + defaultConnection + ", transactionConnection: " + transactionConnection); if (!transactionConnection.open()) { return false; } if (!defaultConnection.open()) { transactionConnection.close(); return false; } return true; } @Override public final boolean isOpen() { boolean isOpen = true; isOpen &= (defaultConnection != null) && defaultConnection.isOpen(); isOpen &= (transactionConnection != null) && transactionConnection.isOpen(); return isOpen; } @Override public final void close() { Lock lock = lockX(); try { defaultConnection.close(); transactionConnection.close(); } finally { lock.release(); } } // ---------------------------------------------------------------------------------------- // ABSTRACTS // ---------------------------------------------------------------------------------------- protected abstract ContentValues createContentValues(); /** * Removes all tables which are created in {@link SqlDbDataPortal#onCreate()}. However, backup tables will survive * this call. */ protected abstract void dropRegularTables(); /** * Abstract method for all Selects. * * @param sql * the SQL query * @param selectionArgs * You may include ?s in where clause in the query, which will be replaced by the values from * selectionArgs. The values will be bound as Strings. * @throws UncheckedSqlException * @return the row ID of the newly inserted row, or -1 if an error occurred */ public abstract ICursor execSelect(String sql, String[] selectionArgs) throws UncheckedSqlException; /** * Abstract method for inserting a row into the database. * * @param table * the table to insert the row into * @param values * this map contains the initial column values for the row. The keys should be the column names and the * values the column values * @throws UncheckedSqlException * @return the row ID of the newly inserted row, or -1 if an error occurred */ public abstract long insertOrThrow(String table, ContentValues values) throws UncheckedSqlException; /** * Inserts the rows corresponding to the ContentValues in the given {@link BatchContentValues} as fast as possible. * * @param table * The table to insert the rows. * @param batchContentValues * Contains the ContentValues that should be inserted in a batch as fast as possible. * @param ignoreConflicts * Indicates whether potential conflicts while inserting should be silently ignored. * * @throws UncheckedSqlException */ protected abstract void insertBatch(String table, BatchContentValues batchContentValues, boolean ignoreConflicts) throws UncheckedSqlException; /** * Abstract for all Updates * * @param table * The table to insert the row into * @param values * this map contains the initial column values for the row. The keys should be the column names and the * values the column values * @param whereClause * the optional WHERE clause to apply when updating. Passing null will update all rows. * @throws UncheckedSqlException * @return the number of rows affected */ public abstract int update(String table, ContentValues values, String whereClause, String[] whereArgs) throws UncheckedSqlException; /** * Updates the rows corresponding to the given content values in a batch as fast as possible. * * @param table * The table which to update. * @param batchContentValues * Contains the key-value-pairs to be updated. * @param whereClauseContentValues * Contains the key-value-pairs that restrict the elements to be updated. There is a one-to-one * correspondence between the entries in <code>batchContentValues</code> and the entries in * <code>whereClauseContentValues</code>, i.e. each entry in the latter specify which entry should be * updated with the values in the former. * @throws UncheckedSqlException */ protected abstract void updateBatch(String table, BatchContentValues batchContentValues, BatchContentValues whereClauseContentValues) throws UncheckedSqlException; protected String getWhereCondition(ContentValues cvs) { StringBuilder sql = new StringBuilder(); for (ContentValue cv : cvs) { sql.append(cv.getKey()).append(" =? AND "); } sql.delete(sql.length() - 5, sql.length()); return sql.toString(); } /** * Abstract for all Deletes * * @param table * the table to delete from * @param whereClause * the optional WHERE clause to apply when deleting. Passing null will delete all rows. * @throws UncheckedSqlException * @return the number of rows affected */ public abstract int delete(String table, String whereClause, String[] whereArgs) throws UncheckedSqlException; /** * Abstract for all Query e.g Deletes, Updates */ public abstract void execSQL(String sql) throws UncheckedSqlException; /** * Abstract for some rare queries, which explicitely do not want a lock to be created. (i.e. transaction creation) * * @param connection * On which connection we should operate * @see #execSQL(String) */ public abstract void execSQLNoLock(String sql, ISqlDbConnection connection) throws UncheckedSqlException; // ---------------------------------------------------------------------------------------- // IMPLEMENTATIONS // ---------------------------------------------------------------------------------------- private ArtistIdGenerator artistIdGenerator; protected ArtistIdGenerator getArtistIdGenerator() { if (artistIdGenerator == null) { artistIdGenerator = new ArtistIdGenerator(this); } return artistIdGenerator; } @Override public void resetDatabase() { // execSQL("DELETE FROM " + TblSongs.TBL_NAME); // execSQL("DELETE FROM " + TblArtists.TBL_NAME + " WHERE " // + TblArtists.IS_FAMOUS_ARTIST + " = 0"); // execSQL("DELETE FROM " + TblAlbums.TBL_NAME); // execSQL("DELETE FROM " + TblGenres.TABLE_NAME); // // execSQL("DELETE FROM " + TblTags.TBL_NAME); // execSQL("DELETE FROM " + TblArtistSets.TBL_NAME); // execSQL("DELETE FROM " + TblSongGenres.TABLE_NAME); // execSQL("DELETE FROM " + TblSongCoords.TBL_NAME); // execSQL("DELETE FROM " + TblArtistCoords.TBL_NAME + " WHERE " // + TblArtistCoords.ARTIST_ID + " NOT IN (SELECT " // + TblArtists.ARTIST_ID + " FROM " + TblArtists.TBL_NAME // + " WHERE " + TblArtists.IS_FAMOUS_ARTIST + " = 0" + ")"); // execSQL("DELETE FROM " + TblPlayLog.TBL_NAME); // boolean deleted = application.deleteDatabase(Constants.DB_NAME); // FIXME do we need to clean this tables? backupDurableData(); onUpgrade(0); // Recreate the database from scratch Log.v(TAG, "clearDb: database cleared"); } /** * Moves data which should survive a database reset into backup tables.<br/> * Please note, that the backup table names must begin with "backup_". */ private void backupDurableData() { statisticsDbHelper.backupStatisticsData(); statisticsDbHelper.backupRatingData(); } // ---------------------------------------------------------------------------------------- // SELECTS // ---------------------------------------------------------------------------------------- @Override public List<BaseSong<BaseArtist, BaseAlbum>> getAllSongs() { List<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID; cur = execSelect(sql, new String[] {}); while (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur .getInt(6))); } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public BaseSong<BaseArtist, BaseAlbum> getBaseSongById(int randomId) throws DataUnavailableException { Set<Integer> ids = new HashSet<Integer>(1); ids.add(randomId); return batchGetBaseSongByIds(ids).get(0); } @Override public List<BaseSong<BaseArtist, BaseAlbum>> batchGetBaseSongByIds(Set<Integer> randomIds) throws DataUnavailableException { ICursor cur = null; try { StringBuffer ids = new StringBuffer(randomIds.size() * 4); // I assume an average id length of 3 chars plus the comma for (int id : randomIds) { ids.append(id).append(","); } ids.deleteCharAt(ids.length() - 1); // remove last comma String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.SONG_ID + " IN (%s)"; cur = execSelect(String.format(sql, ids.toString()), null); List<BaseSong<BaseArtist, BaseAlbum>> ret = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(randomIds.size()); while (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); ret.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(6))); } if (ret.size() != randomIds.size()) { throw new DataUnavailableException("Not all ids found!"); } else { return ret; } } finally { if (cur != null) { cur.close(); } } } @Override public HashMap<String, ImportSong> getAllSongsForImport() throws DataUnavailableException { HashMap<String, ImportSong> pathToSongMap = new HashMap<String, ImportSong>(); ICursor cur = null; try { String sql = "SELECT " + "s." + TblSongs.SONG_ID + ", " + "s." + TblSongs.NAME + ", ar." + TblArtists.NAME + ", " + " al." + TblAlbums.ALBUM_NAME + ", al." + TblAlbums.ARTIST_SET_ID + ", s." + TblSongs.DATA + ", " + " s." + TblSongs.DURATION + ", s." + TblSongs.TRACK_NR + ", s." + TblSongs.IMPORT_TIMESTAMP + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + " JOIN " + TblArtists.TBL_NAME + " AS ar ON s." + TblSongs.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " " + " JOIN " + TblAlbums.TBL_NAME + " AS al ON s." + TblSongs.ALBUM_ID + " = al." + TblAlbums.ALBUM_ID; // Log.v(TAG, "getAllSongsForImport: sql: " + sql); cur = execSelect(sql, null); if (cur == null) { throw new DataUnavailableException("cur == null"); } while (cur.moveToNext()) { ImportSong importSong = getImportSongFromCursor(cur); pathToSongMap.put(importSong.getPath(), importSong); } return pathToSongMap; } catch (UncheckedSqlException e) { Log.w(TAG, e); throw new DataUnavailableException(e); } finally { if (cur != null) { cur.close(); } } } /** * Creates an {@link ImportSong} instance out of the given cursor.<br/> * Ensurethe first columns are {@link TblSongs#SONG_ID}, {@link TblSongs#NAME}, {@link TblArtists#NAME}, * {@link TblAlbums#ALBUM_NAME}, {@link TblAlbums#ARTIST_SET_ID}, {@link TblSongs#DATA}, {@link TblSongs#DURATION}, * {@link TblSongs#TRACK_NR}, {@link TblSongs#IMPORT_TIMESTAMP}. * * @param cur * @return */ private ImportSong getImportSongFromCursor(ICursor cur) { Integer jukefoxId = cur.getInt(0); String name = cur.getString(1); String artist = cur.getString(2); String albumName = cur.getString(3); int albumArtistSetId = cur.getInt(4); String path = cur.getString(5); int duration = cur.getInt(6); int track = cur.getInt(7); Date importDate = new Date(cur.getInt(8)); ContentProviderId cpId = null; if (Utils.isNullOrEmpty(name, true)) { name = languageHelper.getUnknownTitleAlias(); } if (Utils.isNullOrEmpty(albumName, true)) { albumName = languageHelper.getUnknownAlbumAlias(); } if (Utils.isNullOrEmpty(artist, true)) { artist = languageHelper.getUnknownArtistAlias(); } ImportAlbum importAlbum = getImportAlbum(albumName, albumArtistSetId); return new ImportSong(name, importAlbum, artist, path, duration, track, cpId, jukefoxId, importDate); } private ImportAlbum getImportAlbum(String albumName, int albumArtistSetId) { ICursor cur = null; try { String sql = "SELECT a." + TblArtists.NAME + " FROM " + TblArtistSets.TBL_NAME + " aset JOIN " + TblArtists.TBL_NAME + " a ON aset." + TblArtistSets.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " WHERE aset." + TblArtistSets.ARTIST_SET_ID + " = ?"; // Log.v(TAG, "getImportAlbum: sql: " + sql); cur = execSelect(sql, new String[] { "" + albumArtistSetId }); ImportAlbum album = new ImportAlbum(albumName); while (cur.moveToNext()) { album.addArtistName(cur.getString(0)); } return album; } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseSong<BaseArtist, BaseAlbum>> getSongListForAlbum(BaseAlbum album) { List<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "WHERE s." + TblSongs.ALBUM_ID + "=? " + "ORDER BY s." + TblSongs.TRACK_NR + " ASC, " + TblSongs.DATA + " ASC"; cur = execSelect(sql, new String[] { "" + album.getId() }); while (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur .getInt(4))); } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public List<PlaylistSong<BaseArtist, BaseAlbum>> getSongListForPaths(List<String> paths) { List<PlaylistSong<BaseArtist, BaseAlbum>> songs = new ArrayList<PlaylistSong<BaseArtist, BaseAlbum>>(); for (String path : paths) { PlaylistSong<BaseArtist, BaseAlbum> song; try { song = getSongForPath(path); } catch (DataUnavailableException e) { song = null; } if (song != null) { songs.add(song); } } return songs; } @Override public List<BaseSong<BaseArtist, BaseAlbum>> getSongListForIds(Vector<KdTreePoint<Integer>> points) { ArrayList<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.SONG_ID + "=?"; for (KdTreePoint<Integer> point : points) { try { cur = execSelect(sql, new String[] { "" + point.getID() }); if (cur == null) { Log.w(TAG, "DB cursor is null!"); return songs; } if (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(6))); } } finally { if (cur != null) { cur.close(); cur = null; } } } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public List<Pair<BaseSong<BaseArtist, BaseAlbum>, KdTreePoint<Integer>>> getSongListForIds2( Vector<KdTreePoint<Integer>> points) { ArrayList<Pair<BaseSong<BaseArtist, BaseAlbum>, KdTreePoint<Integer>>> songs = new ArrayList<Pair<BaseSong<BaseArtist, BaseAlbum>, KdTreePoint<Integer>>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.SONG_ID + "=?"; for (KdTreePoint<Integer> point : points) { try { cur = execSelect(sql, new String[] { "" + point.getID() }); if (cur == null) { Log.w(TAG, "DB cursor is null!"); return songs; } if (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); songs.add(new Pair<BaseSong<BaseArtist, BaseAlbum>, KdTreePoint<Integer>>( new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur .getInt(6)), point)); } } finally { if (cur != null) { cur.close(); cur = null; } } } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public PlaylistSong<BaseArtist, BaseAlbum> getSongForPath(String path) throws DataUnavailableException { return getSongForPath(path, true); } @Override public PlaylistSong<BaseArtist, BaseAlbum> getSongForPath(String path, boolean caseSensitive) throws DataUnavailableException { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.DATA + "=?"; ICursor cur = null; try { cur = execSelect(sql, new String[] { path }); if (cur == null) { Log.w(TAG, "getSongForPath: DB cursor is null!"); throw new DataUnavailableException(); } if (!cur.moveToNext()) { throw new DataUnavailableException(); } BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); return new PlaylistSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, SongSource.MANUALLY_SELECTED, cur.getInt(6)); } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseSong<BaseArtist, BaseAlbum>> getSongsForArtist(BaseArtist artist) { List<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ALBUM_ID + ", " + "alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.ARTIST_ID + "=? " + "ORDER BY s." + TblSongs.ALBUM_ID + ",s." + TblSongs.TRACK_NR + " ASC"; cur = execSelect(sql, new String[] { "" + artist.getId() }); while (cur.moveToNext()) { BaseAlbum album = new BaseAlbum(cur.getInt(2), cur.getString(3)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(4))); } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseSong<BaseArtist, BaseAlbum>> getSongsForGenre(Genre genre) { List<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "JOIN " + TblSongGenres.TABLE_NAME + " AS sg ON s." + TblSongs.SONG_ID + "=sg." + TblSongGenres.SONG_ID + " " + "WHERE sg." + TblSongGenres.GENRE_ID + "=?"; cur = execSelect(sql, new String[] { "" + genre.getId() }); while (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(6))); } return songs; } finally { if (cur != null) { cur.close(); } } } /** * returns songs that have one of the status specified in statuses or the according album has one of the statuses * specified in albumStatuses */ @Override public List<WebDataSong> getWebDataSongsForStatus(SongStatus[] statuses, AlbumStatus[] albumStatuses) { List<WebDataSong> songs = new ArrayList<WebDataSong>(); if (statuses == null || statuses.length == 0) { return songs; } ICursor cur = null; try { // TODO: also read meIds, coords, etc.? StringBuffer buffer = new StringBuffer(); buffer.append("SELECT " + "s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.SONG_STATUS + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", al." + TblAlbums.ALBUM_STATUS + " FROM " + TblSongs.TBL_NAME + " s JOIN " + TblArtists.TBL_NAME + " ar ON s." + TblSongs.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " JOIN " + TblAlbums.TBL_NAME + " al ON s." + TblSongs.ALBUM_ID + " = al." + TblAlbums.ALBUM_ID + " WHERE s." + TblSongs.SONG_STATUS + " = ?"); for (int i = 1; i < statuses.length; i++) { buffer.append(" OR s." + TblSongs.SONG_STATUS + " = ?"); } buffer.append(" OR al." + TblAlbums.ALBUM_STATUS + "= ?"); for (int i = 1; i < statuses.length; i++) { buffer.append(" OR al." + TblAlbums.ALBUM_STATUS + " = ?"); } buffer.append(" ORDER BY s." + TblSongs.ALBUM_ID); String sql = buffer.toString(); Log.v(TAG, "getWebDataSongsForStatus(SongStatus[] statuses, AlbumStatus[] albumStatuses) => sql: " + sql); String[] selectionArgs = new String[statuses.length + albumStatuses.length]; for (int i = 0; i < statuses.length; i++) { selectionArgs[i] = Integer.toString(statuses[i].getValue()); } for (int i = 0; i < albumStatuses.length; i++) { selectionArgs[statuses.length + i] = Integer.toString(albumStatuses[i].getValue()); } cur = execSelect(sql, selectionArgs); if (cur == null) { // TODO again? throw new UncheckedSqlException("cur == null"); } while (cur.moveToNext()) { int songId = cur.getInt(0); String title = cur.getString(1); SongStatus status = SongStatus.getStatusForValue(cur.getInt(2)); int artistId = cur.getInt(3); String artistName = cur.getString(4); int albumId = cur.getInt(5); AlbumStatus albumStatus = AlbumStatus.getStatusFromValue(cur.getInt(6)); // TODO: later also read artistStatus... CompleteArtist artist = new CompleteArtist(artistId, artistName, null, null, null); WebDataSong webDataSong = new WebDataSong(songId, title, null, null, null, status, artist, albumId, albumStatus); songs.add(webDataSong); } return songs; } catch (UncheckedSqlException e) { Log.w(TAG, e); return new ArrayList<WebDataSong>(); } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseArtist> getAllArtists() { // Log.v(TAG, "begin getAllArtists()"); List<BaseArtist> artists = new ArrayList<BaseArtist>(); ICursor cur = null; try { String sql = "SELECT " + TblArtists.ARTIST_ID + ", " + TblArtists.NAME + " FROM " + TblArtists.TBL_NAME + " WHERE " + TblArtists.IS_IN_COLLECTION + " = ?"; cur = execSelect(sql, new String[] { "1" }); while (cur.moveToNext()) { artists.add(new BaseArtist(cur.getInt(0), cur.getString(1))); } return artists; } finally { if (cur != null) { cur.close(); } // Log.v(TAG, "end getAllArtists()"); } } @Override public CompleteArtist getCompleteArtist(BaseArtist baseArtist) throws DataUnavailableException { ICursor cur = null; CompleteArtist artist = null; float[] coords = new float[Constants.DIM]; try { String sql = "SELECT a." + TblArtists.ME_ARTIST_ID + ", a." + TblArtists.ME_NAME + ", " + DbUtils.getCoordString("ac." + TblArtistCoords.COORD_PREFIX) + " FROM " + TblArtists.TBL_NAME + " AS a" + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID + " WHERE a." + TblArtists.ARTIST_ID + "=?"; cur = execSelect(sql, new String[] { "" + baseArtist.getId() }); if (cur.moveToNext()) { int meId = cur.getInt(0); String meName = cur.getString(1); if (!cur.isNull(2)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(2 + i); } } artist = new CompleteArtist(baseArtist, meId, meName, coords); return artist; } else { throw new DataUnavailableException(); // TODO: change to more // meaningful exception } } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseArtist> getArtistsForGenre(Genre genre) { Log.v(TAG, "Get Artist for " + genre.getName()); List<BaseArtist> artists = new ArrayList<BaseArtist>(); ICursor cur = null; try { String sql = "SELECT DISTINCT a." + TblArtists.ARTIST_ID + ", a." + TblArtists.NAME + " FROM " + TblArtists.TBL_NAME + " AS a JOIN " + TblSongs.TBL_NAME + " AS s ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " JOIN " + TblSongGenres.TABLE_NAME + " AS sg ON s." + TblSongs.SONG_ID + "=sg." + TblSongGenres.SONG_ID + " WHERE sg." + TblSongGenres.GENRE_ID + "=?"; // Log.v(TAG, sql); cur = execSelect(sql, new String[] { "" + genre.getId() }); while (cur.moveToNext()) { artists.add(new BaseArtist(cur.getInt(0), cur.getString(1))); } Log.v(TAG, "Number of artists: " + artists.size()); return artists; } finally { if (cur != null) { cur.close(); } } } @Override public CompleteAlbum getCompleteAlbum(BaseAlbum album) throws DataUnavailableException { return getCompleteAlbumById(album.getId()); } @Override public CompleteAlbum getCompleteAlbumById(int albumId) throws DataUnavailableException { ICursor cur = null; List<BaseArtist> artists = null; try { String sql = "SELECT " + TblAlbums.ALBUM_ID + ", " + TblAlbums.ALBUM_NAME + ", " + TblAlbums.ARTIST_SET_ID + ", " + TblAlbums.PCA_COORDS_X + ", " + TblAlbums.PCA_COORDS_Y + ", " + TblAlbums.COLOR + ", " + TblAlbums.ALBUM_STATUS + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ALBUM_ID + "=?"; cur = execSelect(sql, new String[] { "" + albumId }); if (!cur.moveToNext()) { throw new DataUnavailableException(); // TODO: change to more // meaningful exception } String albumName = cur.getString(1); artists = getArtistsForArtistSetId(cur.getInt(2)); float[] coordsPca2D = new float[] { cur.getFloat(3), cur.getFloat(4) }; int color = cur.getInt(5); AlbumStatus status = AlbumStatus.getStatusFromValue(cur.getInt(6)); BaseAlbum baseAlbum = new BaseAlbum(albumId, albumName); List<BaseSong<BaseArtist, BaseAlbum>> songs = getSongListForAlbum(baseAlbum); return new CompleteAlbum(albumId, albumName, coordsPca2D, color, artists, songs, status); } finally { if (cur != null) { cur.close(); } } } @Override public List<ListAlbum> getAllAlbumsAsListAlbums() { // Log.v(TAG, "begin getAllAlbumsAsListAlbums()"); ICursor cur = null; try { List<ListAlbum> albums = new ArrayList<ListAlbum>(); String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " ORDER BY al." + TblAlbums.ALBUM_ID; cur = execSelect(sql, new String[] {}); List<BaseArtist> artists = null; int lastAlbumId = -1; while (cur.moveToNext()) { if (cur.getInt(0) != lastAlbumId) { artists = new ArrayList<BaseArtist>(); albums.add(new ListAlbum(cur.getInt(0), cur.getString(1), artists)); lastAlbumId = cur.getInt(0); } artists.add(new BaseArtist(cur.getInt(2), cur.getString(3))); } return albums; } finally { if (cur != null) { cur.close(); } // Log.v(TAG, "end getAllAlbumsAsListAlbums()"); } } @Override public List<BaseAlbum> getAllAlbumsWithoutAlbumArt() { List<BaseAlbum> albums = new ArrayList<BaseAlbum>(); ICursor cur = null; try { String sql = "SELECT " + TblAlbums.ALBUM_ID + ", " + TblAlbums.ALBUM_NAME + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.HIGH_RES_COVER_PATH + " IS NULL OR " + TblAlbums.LOW_RES_COVER_PATH + " IS NULL"; cur = execSelect(sql, new String[] {}); while (cur.moveToNext()) { albums.add(new BaseAlbum(cur.getInt(0), cur.getString(1))); } return albums; } finally { if (cur != null) { cur.close(); } } } @Override public List<ListAlbum> getAllAlbumsForArtist(BaseArtist artist, boolean includeCompilations) { ICursor cur = null; HashSet<Integer> albumIds = new HashSet<Integer>(); try { List<ListAlbum> albums = new ArrayList<ListAlbum>(); String sql = "SELECT DISTINCT al." + TblAlbums.ALBUM_ID + " FROM " + TblArtistSets.TBL_NAME + " aset " + "JOIN " + TblAlbums.TBL_NAME + " al ON aset." + TblArtistSets.ARTIST_SET_ID + " = al." + TblAlbums.ARTIST_SET_ID + " WHERE aset." + TblArtistSets.ARTIST_ID + "=" + artist.getId(); // Log.v(TAG, sql); cur = execSelect(sql, new String[] {}); if (cur == null) { Log.w(TAG, "DB cursor is null!"); return albums; } while (cur.moveToNext()) { // Log.v(TAG, "getAlbumsForArtist() id: " + cur.getInt(0)); try { int albumId = cur.getInt(0); albumIds.add(albumId); albums.add(getListAlbum(albumId)); } catch (Exception e) { Log.w(TAG, e); } } if (includeCompilations) { if (cur != null) { cur.close(); } String sql2 = "SELECT DISTINCT " + TblSongs.ALBUM_ID + " FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.ARTIST_ID + "=" + artist.getId(); // Log.v(TAG, sql2); cur = execSelect(sql2, new String[] {}); while (cur.moveToNext()) { // Log.v(TAG, "getAlbumsForArtist() id: " + cur.getInt(0)); try { int albumId = cur.getInt(0); if (!albumIds.contains(albumId)) { albums.add(getListAlbum(cur.getInt(0))); } } catch (Exception e) { Log.w(TAG, e); } } } // if (includeCompilations) { // List compilationAlbums = getCompliationAlbumsWithArtist(artist); // } return albums; } finally { if (cur != null) { cur.close(); } } } public ListAlbum getListAlbum(int albumId) throws DataUnavailableException { ICursor cur = null; List<BaseArtist> artists = null; try { String sql = "SELECT " + TblAlbums.ALBUM_NAME + ", " + TblAlbums.ARTIST_SET_ID + ", " + TblAlbums.PCA_COORDS_X + ", " + TblAlbums.PCA_COORDS_Y + ", " + TblAlbums.COLOR + ", " + TblAlbums.ALBUM_STATUS + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ALBUM_ID + "=?"; cur = execSelect(sql, new String[] { "" + albumId }); if (!cur.moveToNext()) { throw new DataUnavailableException(); // TODO: change to more // meaningful exception } String albumName = cur.getString(0); artists = getArtistsForArtistSetId(cur.getInt(1)); return new ListAlbum(albumId, albumName, artists); } finally { if (cur != null) { cur.close(); } } } private List<BaseArtist> getArtistsForArtistSetId(int artistSetId) { List<BaseArtist> artists = new ArrayList<BaseArtist>(); ICursor cur = null; try { String sql = "SELECT arts." + TblArtistSets.ARTIST_ID + ", a." + TblArtists.NAME + " FROM " + TblArtistSets.TBL_NAME + " AS arts JOIN " + TblArtists.TBL_NAME + " AS a ON arts." + TblArtistSets.ARTIST_ID + "=a." + TblArtists.ARTIST_ID + " WHERE arts." + TblArtistSets.ARTIST_SET_ID + "=?"; cur = execSelect(sql, new String[] { "" + artistSetId }); while (cur.moveToNext()) { artists.add(new BaseArtist(cur.getInt(0), cur.getString(1))); } return artists; } finally { if (cur != null) { cur.close(); } } } @Override public List<ListAlbum> getAlbumsForGenre(Genre genre) { Log.v(TAG, "Get Albums for " + genre.getName()); ICursor cur = null; try { List<ListAlbum> albums = new ArrayList<ListAlbum>(); String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " JOIN " + TblSongs.TBL_NAME + " AS s ON al." + TblAlbums.ALBUM_ID + "=s." + TblSongs.ALBUM_ID + " JOIN " + TblSongGenres.TABLE_NAME + " AS sg ON s." + TblSongs.SONG_ID + "=sg." + TblSongGenres.SONG_ID + " WHERE sg." + TblSongGenres.GENRE_ID + "=? ORDER BY al." + TblAlbums.ALBUM_ID + " ASC"; // Log.v(TAG, sql); cur = execSelect(sql, new String[] { "" + genre.getId() }); List<BaseArtist> artists = null; int lastAlbumId = -1; while (cur.moveToNext()) { if (cur.getInt(0) != lastAlbumId) { artists = new ArrayList<BaseArtist>(); albums.add(new ListAlbum(cur.getInt(0), cur.getString(1), artists)); lastAlbumId = cur.getInt(0); } artists.add(new BaseArtist(cur.getInt(2), cur.getString(3))); } return albums; } finally { if (cur != null) { cur.close(); } } } @Override public BaseSong<BaseArtist, BaseAlbum> getBaseSongByMusicExplorerId(int meId) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.ME_SONG_ID + "=?"; cur = execSelect(sql, new String[] { Integer.toString(meId) }); if (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); return new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(6)); } throw new DataUnavailableException(); } finally { if (cur != null) { cur.close(); } } } @Override public BaseSong<BaseArtist, BaseAlbum> getArbitrarySongInTimeRange(int profileId, long fromTimestamp, long toTimestamp) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblPlayLog.ME_SONG_ID + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.SKIPPED + " = 0 AND " + TblPlayLog.ME_SONG_ID + " IS NOT NULL AND " + TblPlayLog.TIMESTAMP + " > " + fromTimestamp + " AND " + TblPlayLog.TIMESTAMP + " < " + toTimestamp + " AND " + TblPlayLog.PROFILE_ID + " = " + profileId; Log.v(TAG, "sql: " + sql); cur = execSelect(sql, null); if (!cur.moveToNext()) { Log.v(TAG, "getArbitrarySongInTimeRange: No song found for time period " + new Date(fromTimestamp) + " to " + new Date(toTimestamp)); return null; } // Log.v(TAG, "Number of songs in period: " + cur.getCount()); // getCount() can no longer be used because of JDBC do { try { int meId = cur.getInt(0); BaseSong<BaseArtist, BaseAlbum> song = getBaseSongByMusicExplorerId(meId); return song; } catch (DataUnavailableException e) { Log.w(TAG, e); } } while (cur.moveToNext()); Log.v(TAG, "could not find a valid song... returning null!"); return null; } finally { if (cur != null) { cur.close(); } } } @Override public BaseSong<BaseArtist, BaseAlbum> getSongCloseToTimeRange(int profileId, long fromTimestamp, long toTimestamp, float toleranceRange, float toleranceGlobal) throws DataUnavailableException { PlayLogEntry before = getLastPlaylogEntryBefore(profileId, fromTimestamp, true, true); PlayLogEntry after = getFirstPlaylogEntryAfter(profileId, toTimestamp, true, true); if (before == null && after == null) { return null; } try { if (before == null) { return getBaseSongByMusicExplorerId(after.getMeId()); } if (after == null) { return getBaseSongByMusicExplorerId(before.getMeId()); } long beforeDiff = fromTimestamp - before.getUtcTime(); long afterDiff = after.getUtcTime() - toTimestamp; if (beforeDiff < afterDiff) { if (!isInTolerance(fromTimestamp, toTimestamp, beforeDiff, toleranceRange, toleranceGlobal)) { Log.v(TAG, "beforeDiff outside tolerance: " + beforeDiff); return null; } return getBaseSongByMusicExplorerId(before.getMeId()); } if (!isInTolerance(fromTimestamp, toTimestamp, afterDiff, toleranceRange, toleranceGlobal)) { Log.v(TAG, "afterDiff outside tolerance: " + afterDiff); return null; } return getBaseSongByMusicExplorerId(after.getMeId()); } catch (DataUnavailableException e) { Log.w(TAG, e); } return null; } private PlayLogEntry getLastPlaylogEntryBefore(int profileId, long timestamp, boolean unskippedOnly, boolean withMeIdOnly) { ICursor cur = null; try { String sql = "SELECT " + TblPlayLog.ME_SONG_ID + "," + TblPlayLog.TIMESTAMP + ", " + TblPlayLog.SKIPPED + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.TIMESTAMP + " < " + timestamp + " AND " + TblPlayLog.PROFILE_ID + " = " + profileId; if (unskippedOnly) { sql += " AND " + TblPlayLog.SKIPPED + " = 0"; } if (withMeIdOnly) { sql += " AND " + TblPlayLog.ME_SONG_ID + " IS NOT NULL"; sql += " AND " + TblPlayLog.ME_SONG_ID + " <> 0"; } sql += " ORDER BY " + TblPlayLog.TIMESTAMP + " DESC LIMIT 1"; cur = execSelect(sql, null); if (!cur.moveToNext()) { Log.v(TAG, "No song found before " + new Date(timestamp)); return null; } PlayLogEntry pe = new PlayLogEntry(); if (!cur.isNull(0) && cur.getInt(0) != 0) { pe.setMeId(cur.getInt(0)); } pe.setUtcTime(cur.getLong(1)); pe.setSkipped(cur.getInt(2) == 0 ? false : true); return pe; } finally { if (cur != null) { cur.close(); } } } private PlayLogEntry getFirstPlaylogEntryAfter(int profileId, long timestamp, boolean unskippedOnly, boolean withMeIdOnly) { ICursor cur = null; try { String sql = "SELECT " + TblPlayLog.ME_SONG_ID + "," + TblPlayLog.TIMESTAMP + ", " + TblPlayLog.SKIPPED + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.TIMESTAMP + " > " + timestamp + " AND " + TblPlayLog.PROFILE_ID + " = " + profileId; if (unskippedOnly) { sql += " AND " + TblPlayLog.SKIPPED + " = 0"; } if (withMeIdOnly) { sql += " AND " + TblPlayLog.ME_SONG_ID + " IS NOT NULL"; sql += " AND " + TblPlayLog.ME_SONG_ID + " <> 0"; } sql += " ORDER BY " + TblPlayLog.TIMESTAMP + " ASC LIMIT 1"; cur = execSelect(sql, null); if (!cur.moveToNext()) { Log.v(TAG, "No song found before " + new Date(timestamp)); return null; } PlayLogEntry pe = new PlayLogEntry(); if (!cur.isNull(0) && cur.getInt(0) != 0) { pe.setMeId(cur.getInt(0)); } pe.setUtcTime(cur.getLong(1)); pe.setSkipped(cur.getInt(2) == 0 ? false : true); return pe; } finally { if (cur != null) { cur.close(); } } } private boolean isInTolerance(long fromTimestamp, long toTimestamp, long diff, float toleranceRange, float toleranceGlobal) { long range = toTimestamp - fromTimestamp; if (diff < Constants.ONE_DAY) { return true; } if ((float) diff / range < toleranceRange) { return true; } long now = System.currentTimeMillis(); long mean = (toTimestamp + fromTimestamp) / 2; if ((float) diff / (now - mean) < toleranceGlobal) { return true; } return false; } @Override public List<PlaylistSong<BaseArtist, BaseAlbum>> getSongsForTimeRange(int profileId, long fromTimestamp, long toTimestamp, int number) { Log.v(TAG, "getSongsForTimeRange: from: " + fromTimestamp + "(" + new Timestamp(fromTimestamp) + ")"); Log.v(TAG, "getSongsForTimeRange: from: " + toTimestamp + "(" + new Timestamp(toTimestamp) + ")"); ICursor cur = null; try { String sql = "SELECT " + TblPlayLog.ME_SONG_ID + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.SKIPPED + " = 0 AND " + TblPlayLog.ME_SONG_ID + " IS NOT NULL AND " + TblPlayLog.TIMESTAMP + " > " + fromTimestamp + " AND " + TblPlayLog.TIMESTAMP + " < " + toTimestamp + " AND " + TblPlayLog.PROFILE_ID + " = " + profileId + " LIMIT " + number; cur = execSelect(sql, null); if (!cur.moveToNext()) { Log .v(TAG, "No songs found for time period " + new Date(fromTimestamp) + " to " + new Date( toTimestamp)); return null; } // Log.v(TAG, "Number of songs in period: " + cur.getCount()); int num = 0; List<PlaylistSong<BaseArtist, BaseAlbum>> songs = new ArrayList<PlaylistSong<BaseArtist, BaseAlbum>>(); do { num++; try { int meId = cur.getInt(0); Log.v(TAG, "try to get song with meId " + meId); songs.add(new PlaylistSong<BaseArtist, BaseAlbum>(getBaseSongByMusicExplorerId(meId), SongSource.TIME_BASED)); } catch (DataUnavailableException e) { Log.w(TAG, e); } } while (cur.moveToNext()); Log.v(TAG, "Number of songs in period: " + num); return songs; } finally { if (cur != null) { cur.close(); } } } @Override public List<Genre> getAllGenres() { Log.v(TAG, "Fetching all genres"); List<Genre> genres = new ArrayList<Genre>(); ICursor cur = null; try { String sql = "SELECT " + TblGenres.GENRE_ID + ", " + TblGenres.NAME + " FROM " + TblGenres.TBL_NAME; cur = execSelect(sql, new String[0]); while (cur.moveToNext()) { genres.add(new Genre(cur.getInt(0), cur.getString(1))); } return genres; } finally { if (cur != null) { cur.close(); } } } @Override public List<Pair<Genre, Integer>> getGenresForArtist(BaseArtist artist) throws DataUnavailableException { List<Pair<Genre, Integer>> genres = new ArrayList<Pair<Genre, Integer>>(); ICursor cur = null; try { String sql = "SELECT count(*), g." + TblGenres.GENRE_ID + ", g." + TblGenres.NAME + " FROM " + TblSongs.TBL_NAME + " AS s JOIN " + TblSongGenres.TABLE_NAME + " AS sg ON sg." + TblSongGenres.SONG_ID + "=s." + TblSongs.SONG_ID + " JOIN " + TblGenres.TBL_NAME + " AS g ON g." + TblGenres.GENRE_ID + "=sg." + TblSongGenres.GENRE_ID + " WHERE s." + TblSongs.ARTIST_ID + "=? GROUP BY g." + TblGenres.GENRE_ID; Log.v(TAG, sql); cur = execSelect(sql, new String[] { "" + artist.getId() }); if (cur == null) { throw new DataUnavailableException(); } // Log.v(TAG, "Cursor size: " + cur.getCount()); int num = 0; while (cur.moveToNext()) { num++; genres.add(new Pair<Genre, Integer>(new Genre(cur.getInt(1), cur.getString(2)), Integer.valueOf(cur .getInt(0)))); } Log.v(TAG, "Cursor size: " + num); return genres; } finally { if (cur != null) { cur.close(); } } } @Override public GenreSongMap getGenreSongMappings() throws DataUnavailableException { ICursor cur = null; try { GenreSongMap genreSongMap = new GenreSongMap(); String sql = "SELECT " + TblSongGenres.GENRE_ID + ", " + TblSongGenres.SONG_ID + " FROM " + TblSongGenres.TABLE_NAME; cur = execSelect(sql, null); while (cur.moveToNext()) { genreSongMap.put(cur.getInt(0), cur.getInt(1)); } return genreSongMap; } finally { if (cur != null) { cur.close(); } } } @Override public String getSongPath(BaseSong<BaseArtist, BaseAlbum> song) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblSongs.DATA + " FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.SONG_ID + " = ?"; cur = execSelect(sql, new String[] { "" + song.getId() }); if (!cur.moveToNext()) { return null; } return cur.getString(0); } catch (UncheckedSqlException e) { throw new DataUnavailableException(e); } finally { if (cur != null) { cur.close(); } } } @Override public HashMap<String, Integer> getSongPathToIdMapping() throws DataUnavailableException { ICursor cur = null; try { HashMap<String, Integer> songPathIdMap = new HashMap<String, Integer>(); // TODO: what about TblSongs.IS_IGNORED?? String sql = "SELECT " + TblSongs.SONG_ID + ", " + TblSongs.DATA + " FROM " + TblSongs.TBL_NAME; cur = execSelect(sql, null); while (cur.moveToNext()) { int songId = cur.getInt(0); String songPath = cur.getString(1); songPathIdMap.put(songPath, songId); } return songPathIdMap; } finally { if (cur != null) { cur.close(); } } } @Override public HashSet<String> getAllSongsPaths() throws DataUnavailableException { ICursor cur = null; try { HashSet<String> paths = new HashSet<String>(); // TODO: what about TblSongs.IS_IGNORED?? String sql = "SELECT " + TblSongs.DATA + " FROM " + TblSongs.TBL_NAME; cur = execSelect(sql, null); while (cur.moveToNext()) { String path = cur.getString(0); paths.add(path); } return paths; } finally { if (cur != null) { cur.close(); } } } @Override public List<String> getSongPathsForAlbumName(String name) { ICursor cur = null; try { ArrayList<String> paths = new ArrayList<String>(); // TODO: what about TblSongs.IS_IGNORED?? String sql = "SELECT " + TblSongs.DATA + " FROM " + TblSongs.TBL_NAME + " s JOIN " + TblAlbums.TBL_NAME + " a ON s." + TblSongs.ALBUM_ID + " = a." + TblAlbums.ALBUM_ID + " WHERE a." + TblAlbums.ALBUM_NAME + " = '" + name + "'"; cur = execSelect(sql, null); while (cur.moveToNext()) { String path = cur.getString(0); paths.add(path); } return paths; } finally { if (cur != null) { cur.close(); } } } @Override public String getAlbumArtPath(BaseAlbum album, boolean lowRes) throws DataUnavailableException { ICursor cur = null; try { String sql = null; if (lowRes) { sql = "SELECT " + TblAlbums.LOW_RES_COVER_PATH + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ALBUM_ID + "=?"; } else { sql = "SELECT " + TblAlbums.HIGH_RES_COVER_PATH + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ALBUM_ID + "=?"; } cur = execSelect(sql, new String[] { "" + album.getId() }); if (cur.moveToNext()) { return cur.getString(0); } else { throw new DataUnavailableException(); // TODO: change to more // meaningful exception } } finally { if (cur != null) { cur.close(); } } } @Override public List<SongCoords> getSongCoords(boolean includeSongWithoutCoords) { // TODO: merge with getSongCoordsAndPcaCoords? (pcaCoords are cheap // here...) ICursor cur = null; try { String acPrefix = "ac." + TblArtistCoords.COORD_PREFIX; String scPrefix = "sc." + TblSongCoords.COORD_PREFIX; List<SongCoords> list = new ArrayList<SongCoords>(); String sql = "SELECT s." + TblSongs.SONG_ID + ", " + DbUtils.getCoordString(scPrefix) + ", " + DbUtils.getCoordString(acPrefix) + " FROM " + TblSongs.TBL_NAME + " s LEFT JOIN " + TblSongCoords.TBL_NAME + " sc ON s." + TblSongs.SONG_ID + " = sc." + TblSongCoords.SONG_ID + " LEFT JOIN " + TblArtists.TBL_NAME + " a ON s." + TblSongs.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID; // Log.v(TAG, "getSongCoords(): " + sql); cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); float[] coords = null; if (!cur.isNull(1)) { // song coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(1 + i); } } else if (!cur.isNull(Constants.DIM + 1)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(Constants.DIM + 1 + i); } } if (!includeSongWithoutCoords && coords == null) { continue; } SongCoords sc = new SongCoords(id, coords); list.add(sc); } return list; } finally { if (cur != null) { cur.close(); } } } @Override public float[] getCoordsForSongById(int songId) throws DataUnavailableException { ICursor cur = null; try { String acPrefix = "ac." + TblArtistCoords.COORD_PREFIX; String scPrefix = "sc." + TblSongCoords.COORD_PREFIX; String sql = "SELECT s." + TblSongs.SONG_ID + ", " + DbUtils.getCoordString(scPrefix) + ", " + DbUtils.getCoordString(acPrefix) + " FROM " + TblSongs.TBL_NAME + " s LEFT JOIN " + TblSongCoords.TBL_NAME + " sc ON s." + TblSongs.SONG_ID + " = sc." + TblSongCoords.SONG_ID + " LEFT JOIN " + TblArtists.TBL_NAME + " a ON s." + TblSongs.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID + " WHERE s." + TblSongs.SONG_ID + "=?"; // Log.v(TAG, "getSongCoords(): " + sql + " " + songId); cur = execSelect(sql, new String[] { Integer.toString(songId) }); if (cur.moveToNext()) { float[] coords = null; if (cur.getColumnCount() > 1) { if (!cur.isNull(1)) { // song coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(1 + i); } } else if (!cur.isNull(Constants.DIM + 1)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(Constants.DIM + 1 + i); } } if (coords != null) { return coords; } } } throw new DataUnavailableException("No coordinates are available for this song"); } catch (Exception e) { throw new DataUnavailableException(e); } finally { if (cur != null) { cur.close(); } } } @Override public float[] getCoordsForAlbum(BaseAlbum album) throws DataUnavailableException { List<BaseSong<BaseArtist, BaseAlbum>> songs = getSongListForAlbum(album); float[] coords = null; for (BaseSong<BaseArtist, BaseAlbum> song : songs) { try { SongCoords sc = getSongCoordsById(song.getId()); coords = sc.getCoords(); if (coords != null) { break; } } catch (DataUnavailableException e) { // Ignore it because we can look at other songs // We throw an exception later if we found no coords } } if (coords == null) { throw new DataUnavailableException(); } return coords; } @Override public boolean hasArtistCoords(int meArtistId) { ICursor cur = null; try { String sql = "SELECT count(*) FROM " + TblArtistCoords.TBL_NAME + " WHERE " + TblArtistCoords.ME_ARTIST_ID + " = ?"; cur = execSelect(sql, new String[] { Integer.toString(meArtistId) }); cur.moveToNext(); return cur.getInt(0) != 0; } finally { if (cur != null) { cur.close(); } } } @Override public SongCoords getSongCoordsById(Integer songId) throws DataUnavailableException { ICursor cur = null; try { String acPrefix = "ac." + TblArtistCoords.COORD_PREFIX; String scPrefix = "sc." + TblSongCoords.COORD_PREFIX; String sql = "SELECT s." + TblSongs.SONG_ID + ", " + DbUtils.getCoordString(scPrefix) + ", " + DbUtils.getCoordString(acPrefix) + " FROM " + TblSongs.TBL_NAME + " s LEFT JOIN " + TblSongCoords.TBL_NAME + " sc ON s." + TblSongs.SONG_ID + " = sc." + TblSongCoords.SONG_ID + " LEFT JOIN " + TblArtists.TBL_NAME + " a ON s." + TblSongs.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID + " WHERE s." + TblSongs.SONG_ID + "=" + songId; cur = execSelect(sql, null); if (cur.moveToNext()) { int id = cur.getInt(0); float[] coords = null; if (!cur.isNull(1)) { // song coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(1 + i); } } else if (!cur.isNull(Constants.DIM + 1)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(Constants.DIM + 1 + i); } } return new SongCoords(id, coords); } else { throw new DataUnavailableException(); } } finally { if (cur != null) { cur.close(); } } } @Override public List<SongCoords> getSongCoordsById(List<Integer> songIds) { ICursor cur = null; ArrayList<SongCoords> songCoords = new ArrayList<SongCoords>(); if (songIds.size() == 0) { return songCoords; } try { String acPrefix = "ac." + TblArtistCoords.COORD_PREFIX; String scPrefix = "sc." + TblSongCoords.COORD_PREFIX; String query = "SELECT s." + TblSongs.SONG_ID + ", " + DbUtils.getCoordString(scPrefix) + ", " + DbUtils .getCoordString(acPrefix) + " FROM " + TblSongs.TBL_NAME + " s LEFT JOIN " + TblSongCoords.TBL_NAME + " sc ON s." + TblSongs.SONG_ID + " = sc." + TblSongCoords.SONG_ID + " LEFT JOIN " + TblArtists.TBL_NAME + " a ON s." + TblSongs.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID + " WHERE s." + TblSongs.SONG_ID + " IN ("; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(query); int lastElementPos = songIds.size() - 1; int count = 0; for (Integer songId : songIds) { stringBuilder.append(songId); if (count != lastElementPos) { stringBuilder.append(","); } else { stringBuilder.append(")"); } count++; } String sql = stringBuilder.toString(); cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); float[] coords = null; if (!cur.isNull(1)) { // song coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(1 + i); } songCoords.add(new SongCoords(id, coords)); } else if (!cur.isNull(Constants.DIM + 1)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(Constants.DIM + 1 + i); } } } return songCoords; } finally { if (cur != null) { cur.close(); } } } /** * Should only be called by the PreloadedDataManager If you need tags get it from the preloaded data * * @return */ @Override public HashMap<Integer, CompleteTag> getCompleteTags(boolean onlyRelevantTags) throws DataUnavailableException { ICursor cur = null; try { HashMap<Integer, CompleteTag> tags = new HashMap<Integer, CompleteTag>(); String sql = "SELECT " + TblTags.TAG_ID + ", " + TblTags.ME_TAG_ID + ", " + TblTags.NAME + ", " + TblTags.MEAN_PLSA_PROB + ", " + TblTags.MEAN_PCA_SPACE_X + ", " + TblTags.MEAN_PCA_SPACE_Y + ", " + TblTags.VARIANCE_PLSA_PROB + ", " + TblTags.VARIANCE_PCA_SPACE + ", " + TblTags.IS_MAP_TAG + ", " + DbUtils.getCoordString(TblTags.COORD_PREFIX) + " FROM " + TblTags.TBL_NAME; if (onlyRelevantTags) { sql += " WHERE " + TblTags.IS_RELEVANT + "=1"; } cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); int meId = cur.getInt(1); String name = cur.getString(2); Float meanPlsaProb = cur.isNull(3) ? null : cur.getFloat(3); Float meanPcaSpaceX = cur.isNull(4) ? null : cur.getFloat(4); Float meanPcaSpaceY = cur.isNull(5) ? null : cur.getFloat(5); Float varPlsaProb = cur.isNull(6) ? null : cur.getFloat(6); Float varPcaSpace = cur.isNull(7) ? null : cur.getFloat(7); int isMapTag = cur.isNull(8) ? 0 : cur.getInt(8); float[] coords = null; if (!cur.isNull(9)) { coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(9 + i); } } tags.put(id, new CompleteTag(id, name, meId, varPlsaProb, varPcaSpace, meanPlsaProb, meanPcaSpaceX, meanPcaSpaceY, coords, isMapTag == 1)); } return tags; } finally { if (cur != null) { cur.close(); } } } @Override public CompleteTag getCompleteTagById(int tagId) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblTags.TAG_ID + ", " + TblTags.ME_TAG_ID + ", " + TblTags.NAME + ", " + TblTags.MEAN_PLSA_PROB + ", " + TblTags.MEAN_PCA_SPACE_X + ", " + TblTags.MEAN_PCA_SPACE_Y + ", " + TblTags.VARIANCE_PLSA_PROB + ", " + TblTags.VARIANCE_PCA_SPACE + ", " + TblTags.IS_MAP_TAG + ", " + DbUtils.getCoordString(TblTags.COORD_PREFIX) + " FROM " + TblTags.TBL_NAME + " WHERE " + TblTags.TAG_ID + "=" + tagId; cur = execSelect(sql, null); if (!cur.moveToNext()) { return null; } int id = cur.getInt(0); int meId = cur.getInt(1); String name = cur.getString(2); Float meanPlsaProb = cur.isNull(3) ? null : cur.getFloat(3); Float meanPcaSpaceX = cur.isNull(4) ? null : cur.getFloat(4); Float meanPcaSpaceY = cur.isNull(5) ? null : cur.getFloat(5); Float varPlsaProb = cur.isNull(6) ? null : cur.getFloat(6); Float varPcaSpace = cur.isNull(7) ? null : cur.getFloat(7); int isMapTag = cur.isNull(8) ? 0 : cur.getInt(8); float[] coords = null; if (!cur.isNull(9)) { coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(9 + i); } } return new CompleteTag(id, name, meId, varPlsaProb, varPcaSpace, meanPlsaProb, meanPcaSpaceX, meanPcaSpaceY, coords, isMapTag == 1); } finally { if (cur != null) { cur.close(); } } } @Override public Integer getTagId(String tagName, boolean onlyRelevantTags) throws DataUnavailableException { ICursor cur = null; try { tagName = DbUtils.escapeString(tagName); String sql = "SELECT " + TblTags.TAG_ID + " FROM " + TblTags.TBL_NAME + " WHERE " + TblTags.NAME + " = '" + tagName + "'"; if (onlyRelevantTags) { sql += " AND " + TblTags.IS_RELEVANT + "=1"; } cur = execSelect(sql, null); if (!cur.moveToNext()) { return null; } return cur.getInt(0); } finally { if (cur != null) { cur.close(); } } } @Override public List<MapTag> getMapTags() { ICursor cur = null; try { List<MapTag> tags = new ArrayList<MapTag>(); String sql = "SELECT " + TblTags.TAG_ID + ", " + TblTags.NAME + ", " + TblTags.MEAN_PCA_SPACE_X + ", " + TblTags.MEAN_PCA_SPACE_Y + ", " + TblTags.VARIANCE_PCA_SPACE + ", " + DbUtils.getCoordString(TblTags.COORD_PREFIX) + " FROM " + TblTags.TBL_NAME + " WHERE " + TblTags.IS_MAP_TAG + "=1"; cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); String name = cur.getString(1); Float meanPcaSpaceX = cur.isNull(2) ? 0 : cur.getFloat(2); Float meanPcaSpaceY = cur.isNull(3) ? 0 : cur.getFloat(3); float varPcaSpace = cur.isNull(4) ? 0f : cur.getFloat(4); float[] coords = null; if (!cur.isNull(5)) { coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(5 + i); } } float[] means = new float[] { meanPcaSpaceX, meanPcaSpaceY }; MapTag tag = new MapTag(id, name, means, varPcaSpace); tags.add(tag); } return tags; } finally { if (cur != null) { cur.close(); } } } @Override public List<MapTag> getHighestVarianceTags(int numTags) { ICursor cur = null; try { List<MapTag> tags = new ArrayList<MapTag>(); String sql = "SELECT " + TblTags.TAG_ID + ", " + TblTags.NAME + ", " + TblTags.MEAN_PCA_SPACE_X + ", " + TblTags.MEAN_PCA_SPACE_Y + ", " + TblTags.VARIANCE_PCA_SPACE + " FROM " + TblTags.TBL_NAME + " WHERE " + TblTags.IS_RELEVANT + "=1 ORDER BY " + TblTags.VARIANCE_PLSA_PROB + " DESC LIMIT " + numTags; cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); String name = cur.getString(1); Float meanPcaSpaceX = cur.isNull(2) ? 0 : cur.getFloat(2); Float meanPcaSpaceY = cur.isNull(3) ? 0 : cur.getFloat(3); float varPcaSpace = cur.isNull(4) ? 0 : cur.getFloat(4); float[] means = new float[] { meanPcaSpaceX, meanPcaSpaceY }; MapTag tag = new MapTag(id, name, means, varPcaSpace); tags.add(tag); } return tags; } finally { if (cur != null) { cur.close(); } } } @Override public void logArtistSetTable() { ICursor cur = null; try { String sql = "SELECT " + TblArtistSets.ARTIST_SET_ID + "," + TblArtistSets.ARTIST_ID + " FROM " + TblArtistSets.TBL_NAME; Log.v(TAG, "getAlbumsForArtist() 1"); cur = execSelect(sql, new String[] {}); // Log.v(TAG, "TblArtistSets num entries: " + cur.getCount()); int num = 0; while (cur.moveToNext()) { num++; Log.v(TAG, "set id: " + cur.getInt(0) + ", artist id: " + cur.getInt(1)); } Log.v(TAG, "TblArtistSets num entries: " + num); Log.v(TAG, "getAlbumsForArtist() 3"); } finally { if (cur != null) { cur.close(); } } } @Override public List<MapAlbum> getAllMapAlbums() { ICursor cur = null; try { List<MapAlbum> albums = new ArrayList<MapAlbum>(); String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", al." + TblAlbums.PCA_COORDS_X + ", al." + TblAlbums.PCA_COORDS_Y + ", al." + TblAlbums.COLOR + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " WHERE " + TblAlbums.PCA_COORDS_X + " IS NOT NULL"; cur = execSelect(sql, new String[] {}); List<BaseArtist> artists = null; int lastAlbumId = -1; while (cur.moveToNext()) { // Log.v(TAG, "getAllMapAlbums() id: " + cur.getInt(0) // + ", name: " + cur.getString(1) + ", artist: " // + cur.getString(6)); if (cur.getInt(0) != lastAlbumId) { artists = new ArrayList<BaseArtist>(); albums.add(new MapAlbum(cur.getInt(0), cur.getString(1), new float[] { cur.getFloat(2), cur.getFloat(3) }, cur.getInt(4), artists)); lastAlbumId = cur.getInt(0); // Log.v(TAG, "Returned map album with color " + // Integer.toHexString(albums.get(albums.size()-1).getColor())); } artists.add(new BaseArtist(cur.getInt(5), cur.getString(6))); } Log.v(TAG, "Returned " + albums.size() + " map albums"); return albums; } finally { if (cur != null) { cur.close(); } } } @Override public MapAlbum getMapAlbum(BaseAlbum album) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", al." + TblAlbums.PCA_COORDS_X + ", al." + TblAlbums.PCA_COORDS_Y + ", al." + TblAlbums.COLOR + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " WHERE " + TblAlbums.ALBUM_ID + "=" + album.getId() + " AND " + TblAlbums.PCA_COORDS_X + " IS NOT NULL"; cur = execSelect(sql, new String[] {}); List<BaseArtist> artists = null; MapAlbum mapAlbum = null; while (cur.moveToNext()) { if (artists == null) { artists = new ArrayList<BaseArtist>(); mapAlbum = new MapAlbum(cur.getInt(0), cur.getString(1), new float[] { cur.getFloat(2), cur.getFloat(3) }, cur.getInt(4), artists); } artists.add(new BaseArtist(cur.getInt(5), cur.getString(6))); } if (artists == null) { throw new DataUnavailableException(); } return mapAlbum; } finally { if (cur != null) { cur.close(); } } } @Override public MapAlbum getMapAlbumBySong(BaseSong<? extends BaseArtist, ? extends BaseAlbum> song) throws DataUnavailableException { if (song == null) { return null; } ICursor cur = null; try { MapAlbum album = null; String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", al." + TblAlbums.PCA_COORDS_X + ", al." + TblAlbums.PCA_COORDS_Y + ", al." + TblAlbums.COLOR + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " JOIN " + TblSongs.TBL_NAME + " s ON al." + TblAlbums.ALBUM_ID + " = s." + TblSongs.ALBUM_ID + " WHERE " + TblSongs.SONG_ID + "=" + song.getId(); cur = execSelect(sql, new String[] {}); List<BaseArtist> artists = null; int lastAlbumId = -1; while (cur.moveToNext()) { if (cur.getInt(0) != lastAlbumId) { artists = new ArrayList<BaseArtist>(); album = new MapAlbum(cur.getInt(0), cur.getString(1), new float[] { cur.getFloat(2), cur.getFloat(3) }, cur.getInt(4), artists); lastAlbumId = cur.getInt(0); } artists.add(new BaseArtist(cur.getInt(5), cur.getString(6))); } return album; } finally { if (cur != null) { cur.close(); } } } @Override public int getRandomSongId() throws DataUnavailableException { ICursor cur = null; try { int[] bounds = getSongIdBounds(); Log.v(TAG, "b1: " + bounds[0] + " b2: " + bounds[1]); int diff = bounds[1] - bounds[0]; if (diff < 1) { throw new DataUnavailableException(); } Random random = RandomProvider.getRandom(); int rnd = random.nextInt(diff); rnd += bounds[0]; String sql = "SELECT " + TblSongs.SONG_ID + " FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.SONG_ID + " >= " + rnd; cur = execSelect(sql, null); if (cur.moveToNext()) { return cur.getInt(0); } throw new DataUnavailableException(); } finally { if (cur != null) { cur.close(); } } } private int getSongId(ImportSong song) throws DataUnavailableException { ICursor cur = null; try { String sql = String.format("SELECT %s FROM %s WHERE %s=?;", TblSongs.SONG_ID, TblSongs.TBL_NAME, TblSongs.DATA); cur = execSelect(sql, new String[] { song.getPath() }); if (cur.moveToNext()) { return cur.getInt(0); } throw new DataUnavailableException(); } finally { if (cur != null) { cur.close(); } } } private int[] getSongIdBounds() throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT min(" + TblSongs.SONG_ID + "), max(" + TblSongs.SONG_ID + ") FROM " + TblSongs.TBL_NAME; cur = execSelect(sql, null); if (cur.moveToNext()) { int[] bounds = new int[] { cur.getInt(0), cur.getInt(1) }; return bounds; } throw new DataUnavailableException(); } finally { if (cur != null) { cur.close(); } } } @Override public List<Integer> getSongIdsForAlbum(int albumId) { ICursor cur = null; try { List<Integer> list = new ArrayList<Integer>(); String sql = "SELECT " + TblSongs.SONG_ID + " FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.ALBUM_ID + " = ?"; // Log.v(TAG, "getSongIdsForAlbum(): " + sql); cur = execSelect(sql, new String[] { Integer.toString(albumId) }); while (cur.moveToNext()) { list.add(cur.getInt(0)); } return list; } finally { if (cur != null) { cur.close(); } } } @Override public Integer getMusicExplorerArtistId(BaseArtist artist) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblArtists.ME_ARTIST_ID + " FROM " + TblArtists.TBL_NAME + " WHERE " + TblArtists.ARTIST_ID + "=?"; cur = execSelect(sql, new String[] { "" + artist.getId() }); if (cur.moveToNext()) { if (cur.isNull(0)) { return null; } return Integer.valueOf(cur.getInt(0)); } else { throw new DataUnavailableException("No data was found."); } } catch (UncheckedSqlException e) { Log.w(TAG, e); throw new DataUnavailableException(e); } finally { if (cur != null) { cur.close(); } } } @Override public List<PreloadedSongInfo> getPreloadedSongInfo() { ICursor cur = null; try { String acPrefix = "ac." + TblArtistCoords.COORD_PREFIX; String scPrefix = "sc." + TblSongCoords.COORD_PREFIX; List<PreloadedSongInfo> list; list = new ArrayList<PreloadedSongInfo>(); String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.SONG_PCA_X + ", s." + TblSongs.SONG_PCA_Y + ", " + DbUtils.getCoordString(scPrefix) + ", " + DbUtils.getCoordString(acPrefix) + " FROM " + TblSongs.TBL_NAME + " s LEFT JOIN " + TblSongCoords.TBL_NAME + " sc ON s." + TblSongs.SONG_ID + " = sc." + TblSongCoords.SONG_ID + " LEFT JOIN " + TblArtists.TBL_NAME + " a ON s." + TblSongs.ARTIST_ID + " = a." + TblArtists.ARTIST_ID + " LEFT JOIN " + TblArtistCoords.TBL_NAME + " ac ON a." + TblArtists.ME_ARTIST_ID + " = ac." + TblArtistCoords.ME_ARTIST_ID; Log.v(TAG, "getSongCoordsAndPcaCoords(): " + sql); cur = execSelect(sql, null); while (cur.moveToNext()) { int id = cur.getInt(0); float[] pcaCoords = null; if (!cur.isNull(1)) { pcaCoords = new float[2]; pcaCoords[0] = cur.getFloat(1); pcaCoords[1] = cur.getFloat(2); } float[] coords = null; if (!cur.isNull(3)) { // song coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(3 + i); } } else if (!cur.isNull(Constants.DIM + 3)) { // artist coords available coords = new float[Constants.DIM]; for (int i = 0; i < Constants.DIM; i++) { coords[i] = cur.getFloat(Constants.DIM + 3 + i); } } PreloadedSongInfo sc = new PreloadedSongInfo(id, coords, pcaCoords); list.add(sc); } return list; } finally { if (cur != null) { cur.close(); } } } @Override public Integer getMusicExplorerIdForSong(BaseSong<BaseArtist, BaseAlbum> song) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblSongs.ME_SONG_ID + " FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.SONG_ID + "=?"; cur = execSelect(sql, new String[] { "" + song.getId() }); if (cur.moveToNext()) { if (cur.isNull(0)) { return null; } return Integer.valueOf(cur.getInt(0)); } return null; } finally { if (cur != null) { cur.close(); } } } @Override public PlayLogSendEntity getPlayLogString(int profileId, int playLogVersion, int coordinateVersion, long lastSentId) throws DataUnavailableException { PlayLogSendEntity sendEntity = new PlayLogSendEntity(); String log = ""; ICursor c = null; try { String sql = "SELECT " + TblPlayLog.PLAY_LOG_ID + "," + TblPlayLog.TIMESTAMP + "," + TblPlayLog.TIME_ZONE_OFFSET + ", " + TblPlayLog.ME_ARTIST_ID + "," + TblPlayLog.ME_SONG_ID + "," + TblPlayLog.PLAY_MODE + "," + TblPlayLog.SKIPPED + "," + TblPlayLog.SONG_SOURCE + "," + TblPlayLog.CONTEXT + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.PLAY_LOG_ID + " > " + lastSentId + " AND " + TblPlayLog.PROFILE_ID + " = " + profileId; StringBuilder logString = new StringBuilder(); // Write UserId into String String uid = languageHelper.getUniqueId(); Log.v(TAG, "UID: " + uid); logString.append("UID;" + uid + ";PLAYLOG_VERSION;" + playLogVersion + ";VERSION;" + coordinateVersion + "#"); c = execSelect(sql, null); int columnnumbers = c.getColumnCount(); if (!c.moveToNext()) { Log.v(TAG, "No new log entries exist since logid: " + lastSentId); return null; } // Log.v(TAG, "Number of Entries: " + c.getCount() // + " Number of Columns: " + columnnumbers); int num = 0; do { if (num != 0) { logString.append("#"); } num++; sendEntity.lastId = c.getInt(0); for (int j = 1; j < columnnumbers; j++) { logString.append(c.getString(j)); if (!(j == columnnumbers - 1)) { logString.append(";"); } } } while (c.moveToNext()); Log.v(TAG, "Number of Entries: " + num + " Number of Columns: " + columnnumbers); log = logString.toString(); } catch (Exception e) { Log.w(TAG, e); } finally { if (c != null) { c.close(); } } sendEntity.logString = log; return sendEntity; } @Override public List<ListAlbum> findAlbumBySearchString(String searchTerm, int maxResults) { ICursor cur = null; try { String escapedSearchTerm = DbUtils.escapeString(searchTerm); List<ListAlbum> albums = new ArrayList<ListAlbum>(); String sql = "SELECT al." + TblAlbums.ALBUM_ID + ", al." + TblAlbums.ALBUM_NAME + ", ar." + TblArtists.ARTIST_ID + ", ar." + TblArtists.NAME + " FROM " + TblAlbums.TBL_NAME + " al JOIN " + TblArtistSets.TBL_NAME + " aset ON al." + TblAlbums.ARTIST_SET_ID + " = aset." + TblArtistSets.ARTIST_SET_ID + " JOIN " + TblArtists.TBL_NAME + " ar ON aset." + TblArtistSets.ARTIST_ID + " = ar." + TblArtists.ARTIST_ID + " WHERE al." + TblAlbums.ALBUM_NAME + " LIKE '%" + escapedSearchTerm + "%' LIMIT ?"; cur = execSelect(sql, new String[] { "" + maxResults }); List<BaseArtist> artists = null; int lastAlbumId = -1; while (cur.moveToNext()) { if (cur.getInt(0) != lastAlbumId) { artists = new ArrayList<BaseArtist>(); albums.add(new ListAlbum(cur.getInt(0), cur.getString(1), artists)); lastAlbumId = cur.getInt(0); } artists.add(new BaseArtist(cur.getInt(2), cur.getString(3))); } return albums; } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseArtist> findArtistBySearchString(String searchTerm, int maxResults) { List<BaseArtist> artists = new ArrayList<BaseArtist>(); ICursor cur = null; try { String escapedSearchTerm = DbUtils.escapeString(searchTerm); String sql = "SELECT " + TblArtists.ARTIST_ID + ", " + TblArtists.NAME + " FROM " + TblArtists.TBL_NAME + " WHERE " + TblArtists.IS_IN_COLLECTION + " = 1" + " AND " + TblArtists.NAME + " LIKE '%" + escapedSearchTerm + "%' LIMIT ?"; cur = execSelect(sql, new String[] { "" + maxResults }); while (cur.moveToNext()) { artists.add(new BaseArtist(cur.getInt(0), cur.getString(1))); } return artists; } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseArtist> findFamousArtistBySearchString(String searchTerm, int maxResults) { List<BaseArtist> artists = new ArrayList<BaseArtist>(); ICursor cur = null; try { String escapedSearchTerm = DbUtils.escapeString(searchTerm); String sql = "SELECT " + TblArtists.ARTIST_ID + ", " + TblArtists.NAME + " FROM " + TblArtists.TBL_NAME + " WHERE " + TblArtists.IS_FAMOUS_ARTIST + " = 1" + " AND " + TblArtists.NAME + " LIKE '%" + escapedSearchTerm + "%' LIMIT ?"; cur = execSelect(sql, new String[] { "" + maxResults }); while (cur.moveToNext()) { artists.add(new BaseArtist(cur.getInt(0), cur.getString(1))); } return artists; } finally { if (cur != null) { cur.close(); } } } @Override public List<BaseSong<BaseArtist, BaseAlbum>> findTitleBySearchString(String searchTerm, int maxResults) { List<BaseSong<BaseArtist, BaseAlbum>> songs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(); ICursor cur = null; try { String escapedSearchTerm = DbUtils.escapeString(searchTerm); String sql = "SELECT s." + TblSongs.SONG_ID + ", s." + TblSongs.NAME + ", s." + TblSongs.ARTIST_ID + ", " + "a." + TblArtists.NAME + ", s." + TblSongs.ALBUM_ID + ", alb." + TblAlbums.ALBUM_NAME + ", s." + TblSongs.DURATION + " " + "FROM " + TblSongs.TBL_NAME + " AS s " + "JOIN " + TblArtists.TBL_NAME + " AS a ON a." + TblArtists.ARTIST_ID + "=s." + TblSongs.ARTIST_ID + " " + "JOIN " + TblAlbums.TBL_NAME + " AS alb ON s." + TblSongs.ALBUM_ID + "=alb." + TblAlbums.ALBUM_ID + " " + "WHERE s." + TblSongs.NAME + " LIKE '%" + escapedSearchTerm + "%' " + "LIMIT ?"; cur = execSelect(sql, new String[] { "" + maxResults }); if (cur == null) { Log.w(TAG, "DB cursor is null!"); return songs; } while (cur.moveToNext()) { BaseArtist artist = new BaseArtist(cur.getInt(2), cur.getString(3)); BaseAlbum album = new BaseAlbum(cur.getInt(4), cur.getString(5)); songs.add(new BaseSong<BaseArtist, BaseAlbum>(cur.getInt(0), cur.getString(1), artist, album, cur.getInt(6))); } return songs; } finally { if (cur != null) { cur.close(); } } } @Override public void printPlayLog(int profileId) { ICursor cur = null; try { String sql = "SELECT " + TblPlayLog.ME_SONG_ID + "," + TblPlayLog.TIMESTAMP + "," + TblPlayLog.SKIPPED + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.PROFILE_ID + " = " + profileId; cur = execSelect(sql, null); while (cur.moveToNext()) { Integer meId = cur.isNull(0) ? null : cur.getInt(0); long timestamp = cur.getLong(1); int skipped = cur.getInt(2); Date date = new Date(timestamp); Log.v(TAG, "id: " + meId + ", timestamp: " + timestamp + ", date: " + date + ", skipped: " + skipped); } } finally { if (cur != null) { cur.close(); } } } @Override public boolean isSongInRecentHistory(int playerModelId, BaseSong<BaseArtist, BaseAlbum> baseSong, int equalSongAvoidanceNumber) throws DataUnavailableException { boolean result = false; ICursor c = null; try { String sql = "SELECT " + TblPlayLog.SONG_ID + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.PROFILE_ID + " = " + playerModelId + " ORDER BY " + TblPlayLog.TIMESTAMP + " DESC " + " LIMIT " + equalSongAvoidanceNumber; c = execSelect(sql, null); if (!c.moveToNext()) { Log.v(TAG, "No logs detected!"); throw new DataUnavailableException("No logs detected!"); } do { result |= c.getInt(0) == baseSong.getId(); } while (c.moveToNext()); } catch (Exception e) { Log.w(TAG, e); } finally { if (c != null) { try { c.close(); } catch (Exception e) { } } } return result; } @Override public boolean isArtistInRecentHistory(int playerModelId, BaseArtist baseArtist, int similarArtistAvoidanceNumber) throws DataUnavailableException { boolean result = false; ICursor c = null; try { String sql = "SELECT " + TblPlayLog.ARTIST_ID + " FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.PROFILE_ID + " = " + playerModelId + " ORDER BY " + TblPlayLog.TIMESTAMP + " DESC " + " LIMIT " + similarArtistAvoidanceNumber; c = execSelect(sql, null); if (!c.moveToNext()) { Log.v(TAG, "No logs detected!"); throw new DataUnavailableException("No logs detected!"); } do { result |= c.getInt(0) == baseArtist.getId(); } while (c.moveToNext()); } catch (Exception e) { Log.w(TAG, e); } finally { if (c != null) { try { c.close(); } catch (Exception e) { } } } return result; } // ---------------------------------------------------------------------------------------- // INSERTS // ---------------------------------------------------------------------------------------- @Override public int insertSong(ImportSong s) throws DataWriteException { // Log.v(TAG, "insert song: " + s.getName()); int artistId = insertArtist(s.getArtist()); // Log.v(TAG, "insertSong: artistId: " + artistId); HashSet<String> albumArtistNames = s.getAlbum().getArtistNames(); HashSet<Integer> albumArtistIds = insertArtists(albumArtistNames); ArtistSet albumArtistSet = insertArtistSet(albumArtistIds); String albumName = s.getAlbum().getName(); Integer albumId = getAlbumId(albumName, albumArtistSet.getArtistSetId()); if (albumId == null) { albumId = insertAlbum(albumName, albumArtistSet.getArtistSetId()); } return insertSong(s, albumId, artistId); } @Override public void batchInsertSongs(Set<ImportSong> songs) { beginTransaction(); try { // ARTISTS Set<String> artistNames = new HashSet<String>(); Set<ImportAlbum> albums = new HashSet<ImportAlbum>(); for (ImportSong song : songs) { String songArtistName = song.getArtist(); ImportAlbum album = song.getAlbum(); albums.add(album); artistNames.add(songArtistName); for (String artistName : album.getArtistNames()) { artistNames.add(artistName); } } Map<String, Integer> artistIdMap = batchInsertArtists(artistNames); assert artistIdMap.size() == artistNames.size(); // ARTIST-SETS // TODO: ensure thread safety or/and use IdGenerator int maxArtistSetId = getMaxArtistSetId(); String[] fieldNames = new String[] { TblArtistSets.ARTIST_SET_ID, TblArtistSets.ARTIST_ID, TblArtistSets.ARTIST_SET_HASH }; BatchContentValues artistSetsContentValues = new BatchContentValues(fieldNames); Map<ImportAlbum, Integer> albumToArtistSetIdMap = new HashMap<ImportAlbum, Integer>(); Map<Set<Integer>, Integer> artistSet2artistSetIdMap = new HashMap<Set<Integer>, Integer>(); // songloop: for (ImportSong song : songs) { albumloop: for (ImportAlbum album : albums) { Set<Integer> artistIds = new HashSet<Integer>(); for (String artistName : album.getArtistNames()) { Integer e = artistIdMap.get(artistName); assert e != null; artistIds.add(e); } int hash = DbUtils.getArtistSetHash(artistIds); // check if artist set is already scheduled to be inserted and skip in this case if (artistSet2artistSetIdMap.containsKey(artistIds)) { albumToArtistSetIdMap.put(album, artistSet2artistSetIdMap.get(artistIds)); continue albumloop; } List<ArtistSet> artistSets = getArtistSetsForHash(hash); // check if artist set already exists in DB and skip in this case for (ArtistSet as : artistSets) { if (as.getArtistIds().equals(artistIds)) { // ArtistSet does already exist in DB albumToArtistSetIdMap.put(album, as.getArtistSetId()); artistSet2artistSetIdMap.put(artistIds, as.getArtistSetId()); continue albumloop; } } for (int artistId : artistIds) { artistSetsContentValues.put(TblArtistSets.ARTIST_SET_ID, maxArtistSetId + 1); artistSetsContentValues.put(TblArtistSets.ARTIST_ID, artistId); artistSetsContentValues.put(TblArtistSets.ARTIST_SET_HASH, hash); artistSetsContentValues.saveContentValues(); albumToArtistSetIdMap.put(album, maxArtistSetId + 1); artistSet2artistSetIdMap.put(artistIds, maxArtistSetId + 1); } maxArtistSetId++; } if (artistSetsContentValues.size() > 0) { insertBatch(TblArtistSets.TBL_NAME, artistSetsContentValues, false); } artistSetsContentValues = null; // ALBUMS String[] albumFields = new String[] { TblAlbums.ALBUM_NAME, TblAlbums.ARTIST_SET_ID, TblAlbums.ALBUM_STATUS, }; BatchContentValues albumBCVs = new BatchContentValues(albumFields); for (ImportAlbum album : albums) { Integer artistSetId = albumToArtistSetIdMap.get(album); assert artistSetId != null : album.getName(); albumBCVs.put(TblAlbums.ALBUM_NAME, album.getName()); albumBCVs.put(TblAlbums.ARTIST_SET_ID, artistSetId); albumBCVs.put(TblAlbums.ALBUM_STATUS, AlbumStatus.COVER_UNCHECKED.getValue()); albumBCVs.saveContentValues(); } insertBatch(TblAlbums.TBL_NAME, albumBCVs, true); // SONGS String[] songFields = new String[] { TblSongs.ALBUM_ID, TblSongs.ARTIST_ID, TblSongs.DATA, TblSongs.DURATION, TblSongs.NAME, TblSongs.SONG_STATUS, TblSongs.TRACK_NR, TblSongs.IMPORT_TIMESTAMP }; BatchContentValues songBCVs = new BatchContentValues(songFields); for (ImportSong song : songs) { // find album Integer albumId = getAlbumId(song.getAlbum().getName(), albumToArtistSetIdMap.get(song.getAlbum())); // TODO get these ids in one shot. This takes forever! assert albumId != null; songBCVs.put(TblSongs.ALBUM_ID, albumId); songBCVs.put(TblSongs.ARTIST_ID, artistIdMap.get(song.getArtist())); songBCVs.put(TblSongs.DATA, song.getPath()); songBCVs.put(TblSongs.DURATION, song.getDuration()); songBCVs.put(TblSongs.NAME, song.getName()); songBCVs.put(TblSongs.SONG_STATUS, SongStatus.BASE_DATA.getValue()); songBCVs.put(TblSongs.TRACK_NR, song.getTrack()); songBCVs.put(TblSongs.IMPORT_TIMESTAMP, song.getImportDate().getTime()); songBCVs.saveContentValues(); } insertBatch(TblSongs.TBL_NAME, songBCVs, false); setTransactionSuccessful(); } finally { endTransaction(); } // get song IDS for (ImportSong song : songs) { try { song.setJukefoxId(getSongId(song)); } catch (DataUnavailableException e) { Log.w(TAG, "Could not acquire ID of song " + song.getPath()); } } } private Map<String, Integer> getArtistIdMap(Set<String> artistNames) { ICursor cursor = null; try { if (artistNames.size() == 0) { return new HashMap<String, Integer>(); } // TODO fix this. Strings are not properly escaped in the IN clause. String sql = String.format("SELECT %s, %s FROM %s WHERE %s IN (", TblArtists.ARTIST_ID, TblArtists.NAME, TblArtists.TBL_NAME, TblArtists.NAME); StringBuilder query = new StringBuilder(sql); for (String artistName : artistNames) { query.append(DbUtils.formatQueryValue(DbUtils.escapeString(artistName))).append(", "); } query.delete(query.length() - 2, query.length()); query.append(");"); sql = query.toString(); Log.v(TAG, sql); cursor = execSelect(sql, null); Map<String, Integer> artistIdMap = new HashMap<String, Integer>(); while (cursor.moveToNext()) { // Log.v(TAG, "artistIdMap: " + cursor.getString(1) + "->" + cursor.getInt(0)); artistIdMap.put(cursor.getString(1), cursor.getInt(0)); } return artistIdMap; } finally { if (cursor != null) { cursor.close(); } } } private Map<String, Integer> batchInsertArtists(Set<String> artistNames) { // find out which of the artists to be inserted are already in the db Map<String, Integer> artistIdMap = getArtistIdMap(artistNames); // all the artists in artistIdMap do not have to be inserted since they are already existing in the db. // However we have to set inCollection to true. String[] insertFieldNames = new String[] { TblArtists.ARTIST_ID, TblArtists.NAME, TblArtists.IS_FAMOUS_ARTIST, TblArtists.IS_IN_COLLECTION, }; BatchContentValues artistsToInsert = new BatchContentValues(insertFieldNames); BatchContentValues artistsIds = new BatchContentValues(new String[] { TblArtists.ARTIST_ID }); BatchContentValues artistsInCollection = new BatchContentValues(new String[] { TblArtists.IS_IN_COLLECTION }); for (String artistName : artistNames) { if (!artistIdMap.containsKey(artistName)) { // the artist is not in the db so we have to add it later on int nextId = getArtistIdGenerator().nextId(); artistsToInsert.put(TblArtists.ARTIST_ID, nextId); artistsToInsert.put(TblArtists.NAME, artistName); artistsToInsert.put(TblArtists.IS_FAMOUS_ARTIST, 0); artistsToInsert.put(TblArtists.IS_IN_COLLECTION, 1); artistsToInsert.saveContentValues(); artistIdMap.put(artistName, nextId); } else { artistsIds.put(TblArtists.ARTIST_ID, artistIdMap.get(artistName)); artistsIds.saveContentValues(); artistsInCollection.put(TblArtists.IS_IN_COLLECTION, 1); artistsInCollection.saveContentValues(); } } // Now we insert all new artists if (artistsToInsert.size() > 0) { insertBatch(TblArtists.TBL_NAME, artistsToInsert, false); } // and mark all already existing ones to be InCollection if (artistsInCollection.size() > 0) { updateBatch(TblArtists.TBL_NAME, artistsInCollection, artistsIds); } return artistIdMap; } private Integer getAlbumId(String albumName, int artistSetId) { ICursor cur = null; try { String sql = "SELECT " + TblAlbums.ALBUM_ID + " FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ARTIST_SET_ID + " = ? AND " + TblAlbums.ALBUM_NAME + " = ?"; cur = execSelect(sql, new String[] { "" + artistSetId, albumName }); if (!cur.moveToNext()) { return null; } return cur.getInt(0); } finally { if (cur != null) { cur.close(); } } } @Override public void batchUpdateWebData(Set<WebDataSong> songs) { String[] fieldNames = new String[] { TblSongs.ME_SONG_ID, TblSongs.ME_NAME, TblSongs.SONG_STATUS }; BatchContentValues bcvs = new BatchContentValues(fieldNames); BatchContentValues whereBcvs = new BatchContentValues(new String[] { TblSongs.SONG_ID }); for (WebDataSong song : songs) { if (song.getStatus() == SongStatus.WEB_DATA_OK) { updateWebDataArtist(song.getArtist()); replaceSongCoords(song); } bcvs.put(TblSongs.ME_SONG_ID, song.getMeId()); bcvs.put(TblSongs.ME_NAME, song.getMeName()); bcvs.put(TblSongs.SONG_STATUS, song.getStatus().getValue()); bcvs.saveContentValues(); whereBcvs.put(TblSongs.SONG_ID, song.getId()); whereBcvs.saveContentValues(); } updateBatch(TblSongs.TBL_NAME, bcvs, whereBcvs); } @Override public void batchUpdateAlbumCovers(Set<AlbumFetcherResult> albumCovers) { String[] fieldNames = new String[] { TblAlbums.HIGH_RES_COVER_PATH, TblAlbums.LOW_RES_COVER_PATH, TblAlbums.COLOR, TblAlbums.ALBUM_STATUS }; BatchContentValues bcvs = new BatchContentValues(fieldNames); BatchContentValues whereBcvs = new BatchContentValues(new String[] { TblAlbums.ALBUM_ID }); for (AlbumFetcherResult result : albumCovers) { bcvs.put(TblAlbums.HIGH_RES_COVER_PATH, result.getHighResPath()); bcvs.put(TblAlbums.LOW_RES_COVER_PATH, result.getLowResPath()); bcvs.put(TblAlbums.COLOR, result.getColor()); bcvs.put(TblAlbums.ALBUM_STATUS, result.getStatus().getValue()); bcvs.saveContentValues(); whereBcvs.put(TblAlbums.ALBUM_ID, result.getAlbumId()); whereBcvs.saveContentValues(); } updateBatch(TblAlbums.TBL_NAME, bcvs, whereBcvs); } @Override public void batchInsertTags(List<CompleteTag> tags) { String[] fieldNames = new String[2 + Constants.DIM]; fieldNames[0] = TblTags.ME_TAG_ID; fieldNames[1] = TblTags.NAME; for (int i = 0; i < Constants.DIM; i++) { fieldNames[i + 2] = TblTags.COORD_PREFIX + i; } BatchContentValues bcvs = new BatchContentValues(fieldNames); for (CompleteTag tag : tags) { bcvs.put(TblTags.ME_TAG_ID, tag.getMeId()); bcvs.put(TblTags.NAME, tag.getName()); float[] plsaCoords = tag.getPlsaCoords(); for (int i = 0; i < Constants.DIM; i++) { bcvs.put(TblTags.COORD_PREFIX + i, plsaCoords[0]); } bcvs.saveContentValues(); } insertBatch(TblTags.TBL_NAME, bcvs, false); for (CompleteTag tag : tags) { try { tag.setId(getTagId(tag.getName(), false)); } catch (DataUnavailableException e) { e.printStackTrace(); } } } private HashSet<Integer> insertArtists(Set<String> artistNames) throws DataWriteException { HashSet<Integer> artistIds = new HashSet<Integer>(); for (String artistName : artistNames) { int artistId = insertArtist(artistName); artistIds.add(artistId); } return artistIds; } private ArtistSet insertArtistSet(HashSet<Integer> artistIds) { int hash = DbUtils.getArtistSetHash(artistIds); List<ArtistSet> artistSets = getArtistSetsForHash(hash); // Log.v(TAG, "number of artist sets for hash " + hash + ": " // + artistSets.size()); for (ArtistSet as : artistSets) { if (as.getArtistIds().equals(artistIds)) { return as; } } if (artistSets.size() > 0) { Log.v(TAG, "very unlikely to happen..."); for (ArtistSet as : artistSets) { Log.v(TAG, "artistSetId: " + as.getArtistSetId()); } } // TODO: ensure thread safety? int artistSetId = getMaxArtistSetId() + 1; for (int artistId : artistIds) { insertArtistSetEntry(artistSetId, hash, artistId); } return new ArtistSet(artistSetId, artistIds); } private List<ArtistSet> getArtistSetsForHash(int hash) { ICursor cur = null; try { String sql = "SELECT " + TblArtistSets.ARTIST_SET_ID + ", " + TblArtistSets.ARTIST_ID + " FROM " + TblArtistSets.TBL_NAME + " WHERE " + TblArtistSets.ARTIST_SET_HASH + " = ? ORDER BY " + TblArtistSets.ARTIST_SET_ID; cur = execSelect(sql, new String[] { "" + hash }); LinkedList<ArtistSet> artistSets = new LinkedList<ArtistSet>(); int lastSetId = -1; HashSet<Integer> ids = null; while (cur.moveToNext()) { if (lastSetId != cur.getInt(0)) { ids = new HashSet<Integer>(); lastSetId = cur.getInt(0); artistSets.add(new ArtistSet(lastSetId, ids)); } ids.add(cur.getInt(1)); } return artistSets; } finally { if (cur != null) { cur.close(); } } } private int getMaxArtistSetId() { String sql = "SELECT max(" + TblArtistSets.ARTIST_SET_ID + ") FROM " + TblArtistSets.TBL_NAME; ICursor cur = null; try { cur = execSelect(sql, null); if (!cur.moveToNext()) { return 0; } return cur.getInt(0); } finally { if (cur != null) { cur.close(); } } } private void insertArtistSetEntry(int artistSetId, int hash, int artistId) { ContentValues cv = createContentValues(); cv.put(TblArtistSets.ARTIST_SET_ID, artistSetId); cv.put(TblArtistSets.ARTIST_SET_HASH, hash); cv.put(TblArtistSets.ARTIST_ID, artistId); insertOrThrow(TblArtistSets.TBL_NAME, cv); } private int insertAlbum(String albumName, int artistSetId) { ContentValues cv = createContentValues(); cv.put(TblAlbums.ALBUM_NAME, albumName); cv.put(TblAlbums.ARTIST_SET_ID, artistSetId); cv.put(TblAlbums.ALBUM_STATUS, AlbumStatus.COVER_UNCHECKED.getValue()); long id = insertOrThrow(TblAlbums.TBL_NAME, cv); return (int) id; } private int insertSong(ImportSong s, int albumId, int artistId) throws DataWriteException { try { if (s.getPath() == null) { Log.v(TAG, "path == null"); } ContentValues cv = createContentValues(); cv.put(TblSongs.ALBUM_ID, albumId); cv.put(TblSongs.ARTIST_ID, artistId); cv.put(TblSongs.DATA, s.getPath()); cv.put(TblSongs.DURATION, s.getDuration()); cv.put(TblSongs.NAME, s.getName()); cv.put(TblSongs.SONG_STATUS, SongStatus.BASE_DATA.getValue()); cv.put(TblSongs.TRACK_NR, s.getTrack()); cv.put(TblSongs.IMPORT_TIMESTAMP, s.getImportDate().getTime()); long id = insertOrThrow(TblSongs.TBL_NAME, cv); return (int) id; } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private int insertArtist(String name) throws DataWriteException { try { // Log.v(TAG, "insertArtist: " + name); int id = getArtistId(name); if (id != -1) { // artist already exists. make sure it is marked as being part // of the collection String sql = "UPDATE " + TblArtists.TBL_NAME + " SET " + TblArtists.IS_IN_COLLECTION + " = 1 WHERE " + TblArtists.ARTIST_ID + " = " + id; execSQL(sql); } if (id == -1) { ContentValues cv = createContentValues(); cv.put(TblArtists.NAME, name); cv.put(TblArtists.IS_FAMOUS_ARTIST, 0); cv.put(TblArtists.IS_IN_COLLECTION, true); id = (int) insertOrThrow(TblArtists.TBL_NAME, cv); } if (id == -1) { throw new DataWriteException("error inserting artist with name " + name); } return id; } catch (UncheckedSqlException e) { throw new DataWriteException(e); } catch (DataUnavailableException e) { // workaround for getArtistId Exception throw new DataWriteException(e); } } private int getArtistId(String name) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblArtists.ARTIST_ID + " FROM " + TblArtists.TBL_NAME + " WHERE " + TblArtists.NAME + " LIKE ?"; // Log.v(TAG, "sql: " + sql); cur = execSelect(sql, new String[] { name }); if (!cur.moveToNext()) { return -1; } int id = cur.getInt(0); // Log.v(TAG, "getArtistId() => returning " + id); return id; } catch (UncheckedSqlException e) { throw new DataUnavailableException(e); } finally { if (cur != null) { cur.close(); } } } @Override public void insertSongGenreMapping(int genreId, int songId) throws DataWriteException { try { ContentValues cv = createContentValues(); cv.put(TblSongGenres.GENRE_ID, genreId); cv.put(TblSongGenres.SONG_ID, songId); int id = (int) insertOrThrow(TblSongGenres.TABLE_NAME, cv); if (id == -1) { throw new DataWriteException("error inserting song genre mapping: " + songId + "=> " + genreId); } } catch (UncheckedSqlException e) { throw new DataWriteException("error inserting song genre mapping: " + songId + "=> " + genreId); } } @Override public void batchInsertSongGenreMappings(GenreSongMap newMappings) { String[] fieldNames = new String[] { TblSongGenres.GENRE_ID, TblSongGenres.SONG_ID }; BatchContentValues bcvs = new BatchContentValues(fieldNames); for (GenreSongEntry gse : newMappings.getAll()) { bcvs.put(TblSongGenres.GENRE_ID, gse.getGenreId()); bcvs.put(TblSongGenres.SONG_ID, gse.getSongId()); bcvs.saveContentValues(); } insertBatch(TblSongGenres.TABLE_NAME, bcvs, true); } @Override public void insertArtistCoords(int meArtistId, float[] coords) throws DataWriteException { try { ContentValues cv = createContentValues(); cv.put(TblArtistCoords.ME_ARTIST_ID, meArtistId); fillCoordsContentValues(cv, TblArtistCoords.COORD_PREFIX, coords); // TODO When this method is called for a famous artist, the // coordinates may/are (?) already existing // in the database and therefore insertOrThrow will log an error // which is not was is supposed to happen. insertOrThrow(TblArtistCoords.TBL_NAME, cv); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void insertAlbumArtInfo(BaseAlbum album, String highResPath, String lowResPath, int color, AlbumStatus status) throws DataWriteException { try { String where = "" + TblAlbums.ALBUM_ID + "=?"; ContentValues cv = createContentValues(); cv.put(TblAlbums.HIGH_RES_COVER_PATH, highResPath); cv.put(TblAlbums.LOW_RES_COVER_PATH, lowResPath); cv.put(TblAlbums.COLOR, color); update(TblAlbums.TBL_NAME, cv, where, new String[] { "" + album.getId() }); cv.put(TblAlbums.ALBUM_STATUS, status.getValue()); update(TblAlbums.TBL_NAME, cv, where, new String[] { "" + album.getId() }); // TODO does only throw from update } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public int insertGenre(String name) throws DataWriteException { try { int id = getGenreIdByName(name); if (id != -1) { // genre already exists return id; } if (id == -1) { ContentValues cv = createContentValues(); cv.put(TblGenres.NAME, name); id = (int) insertOrThrow(TblGenres.TBL_NAME, cv); } if (id == -1) { throw new DataWriteException("error inserting genre: " + name); } return id; } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private int getGenreIdByName(String name) { ICursor cur = null; try { String sql = "SELECT " + TblGenres.GENRE_ID + " FROM " + TblGenres.TBL_NAME + " WHERE " + TblGenres.NAME + "=?"; cur = execSelect(sql, new String[] { name }); if (cur.moveToNext()) { return cur.getInt(0); } else { return -1; } } finally { if (cur != null) { cur.close(); } } } @Override public long writePlayLogEntry(int profileId, PlaylistSong<BaseArtist, BaseAlbum> song, long utcTime, int timeZoneOffset, int dayOfWeek, int hourOfDay, boolean skip, int playMode, AbstractContextResult contextData, int playbackPosition) throws DataWriteException { long id = 0; int meSongId = 0; int meArtistId = 0; // Get MusicExplorer Ids ICursor cur = null; try { String sql = "SELECT s." + TblSongs.ME_SONG_ID + ", a." + TblArtists.ME_ARTIST_ID + " FROM " + TblSongs.TBL_NAME + " AS s " + " JOIN " + TblArtists.TBL_NAME + " AS a ON s." + TblSongs.ARTIST_ID + "=a." + TblArtists.ARTIST_ID + " WHERE s." + TblSongs.SONG_ID + "=" + song.getId(); cur = execSelect(sql, new String[] {}); if (cur != null && cur.moveToNext()) { meSongId = cur.getInt(0); meArtistId = cur.getInt(1); } } finally { if (cur != null) { cur.close(); } } Log.v(TAG, "writeLogEntry: meSongId: " + meSongId + ", meArtistId: " + meArtistId); // Write Entry try { int skipped = 0; if (skip) { skipped = 1; } ContentValues initialValues = createContentValues(); initialValues.put(TblPlayLog.PROFILE_ID, profileId); initialValues.put(TblPlayLog.TIMESTAMP, utcTime); initialValues.put(TblPlayLog.TIME_ZONE_OFFSET, timeZoneOffset); initialValues.put(TblPlayLog.HOUR, hourOfDay); initialValues.put(TblPlayLog.DAY, dayOfWeek); initialValues.put(TblPlayLog.SONG_ID, song.getId()); initialValues.put(TblPlayLog.ARTIST_ID, song.getArtist().getId()); initialValues.put(TblPlayLog.ME_SONG_ID, meSongId); initialValues.put(TblPlayLog.ME_ARTIST_ID, meArtistId); initialValues.put(TblPlayLog.PLAY_MODE, playMode); initialValues.put(TblPlayLog.SKIPPED, skipped); initialValues.put(TblPlayLog.SONG_SOURCE, song.getSongSource().value()); initialValues.put(TblPlayLog.CONTEXT, contextData.createDbString()); initialValues.put(TblPlayLog.PLAYBACK_POSITION, playbackPosition); id = insertOrThrow(TblPlayLog.TBL_NAME, initialValues); Log.d(TAG, "Inserted play log id " + id); // TODO maxrow should be the maxrow per Profile now long maxrow = Constants.DB_ACTIVATED_PLAY_LOG_SIZE; if (id > maxrow) { String sql = "DELETE FROM " + TblPlayLog.TBL_NAME + " WHERE " + TblPlayLog.PLAY_LOG_ID + " < " + (id - maxrow); execSQL(sql); } } catch (Exception e) { Log.w(TAG, e); throw new DataWriteException(e); } return id; } @Override public int insertTag(int meId, String name, float[] coords) throws DataWriteException { try { ContentValues cv = createContentValues(); cv.put(TblTags.ME_TAG_ID, meId); cv.put(TblTags.NAME, name); fillCoordsContentValues(cv, TblTags.COORD_PREFIX, coords); long id = insertOrThrow(TblTags.TBL_NAME, cv); return (int) id; } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public int insertOrGetPlayerModelId(String name) throws DataWriteException { try { int id = getPlayerModelId(name); if (id != -1) { // player model already exists return id; } if (id == -1) { ContentValues cv = createContentValues(); cv.put(TblPlayerModel.NAME, name); id = (int) insertOrThrow(TblPlayerModel.TBL_NAME, cv); } if (id == -1) { throw new DataWriteException("error inserting player model: " + name); } return id; } catch (UncheckedSqlException uncheckedSqlException) { throw new DataWriteException(uncheckedSqlException); } catch (DataUnavailableException e) { throw new DataWriteException(e); } } /** * Gets the player model id ({@link Integer}) of the given name or -1 if it doesn't exist */ private int getPlayerModelId(String name) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblPlayerModel.ID + " FROM " + TblPlayerModel.TBL_NAME + " WHERE " + TblPlayerModel.NAME + " = ?"; cur = execSelect(sql, new String[] { name }); if (cur.moveToNext()) { return cur.getInt(0); } else { return -1; } } catch (UncheckedSqlException e) { throw new DataUnavailableException(e); } finally { if (cur != null) { try { cur.close(); } catch (Exception e1) { } } } } // *** Collection statistics *** // @Override public CollectionProperties getCollectionProperties() { CollectionProperties cp = new CollectionProperties(); try { double avgSongDistance = getKeyValueDouble(NAMESPACE_COLLECTION_PROPERTIES, CP_AVG_SONG_DISTANCE); cp.setAverageSongDistance(avgSongDistance); } catch (DataUnavailableException e) { // Just ignore it. Will be handled in the getters of CollectionProperties. } try { double songDistanceStdDeviation = getKeyValueDouble(NAMESPACE_COLLECTION_PROPERTIES, CP_SONG_DISTANCE_STD_DEVIATION); cp.setSongDistanceStdDeviation(songDistanceStdDeviation); } catch (DataUnavailableException e) { // Just ignore it. Will be handled in the getters of CollectionProperties. } return cp; } @Override public String getKeyValue(String namespace, String key) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT " + TblKeyValue.VALUE + " " + "FROM " + TblKeyValue.TBL_NAME + " " + "WHERE (" + TblKeyValue.NAMESPACE + " = ?) " + " AND (" + TblKeyValue.KEY + " = ?) "; cur = execSelect(sql, new String[] { namespace, key }); if (cur.moveToNext()) { return cur.getString(0); } else { throw new DataUnavailableException(); } } catch (UncheckedSqlException e) { throw new DataUnavailableException(e); } finally { if (cur != null) { try { cur.close(); } catch (Exception e1) { } } } } @Override public int getKeyValueInt(String namespace, String key) throws DataUnavailableException { return Integer.parseInt(getKeyValue(namespace, key)); } @Override public double getKeyValueDouble(String namespace, String key) throws DataUnavailableException { return Double.parseDouble(getKeyValue(namespace, key)); } // ---------------------------------------------------------------------------------------- // UPDATES // ---------------------------------------------------------------------------------------- @Override public void batchInsertFamousArtists(int[] meIds, String[] names, float[][] coords) throws DataWriteException { String[] artistFields = new String[] { TblArtists.ME_ARTIST_ID, TblArtists.NAME, TblArtists.ME_NAME, TblArtists.IS_FAMOUS_ARTIST }; String[] coordsFields = new String[Constants.DIM + 1]; coordsFields[0] = TblArtistCoords.ME_ARTIST_ID; for (int i = 1; i < coordsFields.length; i++) { coordsFields[i] = TblArtistCoords.COORD_PREFIX + String.valueOf(i - 1); } BatchContentValues artistBCVs = new BatchContentValues(artistFields); BatchContentValues coordsBCVs = new BatchContentValues(coordsFields); for (int i = 0; i < meIds.length; i++) { int meId = meIds[i]; String name = names[i]; float[] coord = coords[i]; artistBCVs.put(TblArtists.ME_ARTIST_ID, meId); artistBCVs.put(TblArtists.NAME, name); artistBCVs.put(TblArtists.ME_NAME, name); artistBCVs.put(TblArtists.IS_FAMOUS_ARTIST, true); artistBCVs.saveContentValues(); coordsBCVs.put(TblArtistCoords.ME_ARTIST_ID, meId); for (int j = 0; j < coord.length; j++) { coordsBCVs.put(TblArtistCoords.COORD_PREFIX + String.valueOf(j), coord[j]); } coordsBCVs.saveContentValues(); } insertBatch(TblArtists.TBL_NAME, artistBCVs, true); insertBatch(TblArtistCoords.TBL_NAME, coordsBCVs, true); } @Override public int replaceFamousArtist(int meId, String name, String meName) throws DataWriteException { int artistId; try { artistId = getArtistId(name); if (artistId == -1) { artistId = insertFamousArtist(artistId, meId, name, meName); } else { updateArtistToFamous(artistId, meId, meName); } return artistId; } catch (DataUnavailableException e) { throw new DataWriteException(e); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void updateArtistToFamous(int artistId, int meId, String meName) { ContentValues cv = createContentValues(); cv.put(TblArtists.ME_ARTIST_ID, meId); cv.put(TblArtists.ME_NAME, meName); cv.put(TblArtists.IS_FAMOUS_ARTIST, true); update(TblArtists.TBL_NAME, cv, TblArtists.ARTIST_ID + " = " + artistId, null); // Log.v(TAG, "updated artist to famous: " + artistId); } private int insertFamousArtist(int artistId, int meId, String name, String meName) throws DataWriteException { try { ContentValues cv = createContentValues(); cv.put(TblArtists.ME_ARTIST_ID, meId); cv.put(TblArtists.NAME, name); cv.put(TblArtists.ME_NAME, meName); cv.put(TblArtists.IS_FAMOUS_ARTIST, true); // for some reason, replace does not work as expected (seems to // always create a new row...) long id = insertOrThrow(TblArtists.TBL_NAME, cv); // Log.v(TAG, "insert famous artist, id: " + id); return (int) id; } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void updateUnusedArtists() throws DataWriteException { try { String sql = "UPDATE " + TblArtists.TBL_NAME + " SET " + TblArtists.IS_IN_COLLECTION + "=0 WHERE " + TblArtists.ARTIST_ID + " IN(SELECT " + TblArtists.TBL_NAME + "." + TblArtists.ARTIST_ID + " FROM " + TblArtists.TBL_NAME + " LEFT JOIN " + TblSongs.TBL_NAME + " ON " + TblArtists.TBL_NAME + "." + TblArtists.ARTIST_ID + " = " + TblSongs.TBL_NAME + "." + TblSongs.ARTIST_ID + " WHERE " + TblSongs.TBL_NAME + "." + TblSongs.SONG_ID + " IS NULL)"; // Log.v(TAG, "sql: " + sql); long startTime = System.currentTimeMillis(); execSQL(sql); long endTime = System.currentTimeMillis(); Log.v(TAG, "remove unused artist: time: " + (endTime - startTime)); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void updateWebDataSong(WebDataSong song) throws DataWriteException { try { if (song.getStatus() == SongStatus.WEB_DATA_OK) { updateWebDataArtist(song.getArtist()); replaceSongCoords(song); } ContentValues cv = createContentValues(); cv.put(TblSongs.ME_SONG_ID, song.getMeId()); cv.put(TblSongs.ME_NAME, song.getMeName()); cv.put(TblSongs.SONG_STATUS, song.getStatus().getValue()); update(TblSongs.TBL_NAME, cv, TblSongs.SONG_ID + " = " + song.getId(), null); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void replaceSongCoords(WebDataSong song) { if (song.getCoords() == null) { // TODO: or delete row? return; } ContentValues cv = createContentValues(); cv.put(TblSongCoords.SONG_ID, song.getId()); fillCoordsContentValues(cv, TblSongCoords.COORD_PREFIX, song.getCoords()); if (songHasCoords(song.getId())) { update(TblSongCoords.TBL_NAME, cv, TblSongCoords.SONG_ID + "=" + song.getId(), null); } else { insertOrThrow(TblSongCoords.TBL_NAME, cv); } } private boolean songHasCoords(int songId) { ICursor cur = null; try { String sql = "SELECT " + TblSongCoords.SONG_ID + " FROM " + TblSongCoords.TBL_NAME + " WHERE " + TblSongCoords.SONG_ID + "=" + songId; cur = execSelect(sql, null); if (cur.moveToNext()) { return true; } else { return false; } } finally { if (cur != null) { cur.close(); } } } private boolean artistHasCoords(CompleteArtist artist) { int artistMeId = artist.getMeId(); ICursor cur = null; try { String sql = "SELECT " + TblArtistCoords.ME_ARTIST_ID + " FROM " + TblArtistCoords.TBL_NAME + " WHERE " + TblArtistCoords.ME_ARTIST_ID + "=" + artistMeId; cur = execSelect(sql, null); if (cur.moveToNext()) { return true; } else { return false; } } finally { if (cur != null) { cur.close(); } } } private void updateWebDataArtist(CompleteArtist artist) { replaceArtistCoords(artist); ContentValues cv = createContentValues(); cv.put(TblArtists.ME_ARTIST_ID, artist.getMeId()); cv.put(TblArtists.ME_NAME, artist.getMeName()); update(TblArtists.TBL_NAME, cv, TblArtists.ARTIST_ID + " = " + artist.getId(), null); } private void replaceArtistCoords(CompleteArtist artist) { if (artist.getCoords() == null) { // TODO: or delete row?? return; } ContentValues cv = createContentValues(); cv.put(TblArtistCoords.ME_ARTIST_ID, artist.getMeId()); fillCoordsContentValues(cv, TblArtistCoords.COORD_PREFIX, artist.getCoords()); if (artistHasCoords(artist)) { update(TblArtistCoords.TBL_NAME, cv, TblArtistCoords.ME_ARTIST_ID + "=" + artist.getMeId(), null); } else { insertOrThrow(TblArtistCoords.TBL_NAME, cv); } } @Override public void updateSongsPcaCoords(HashMap<Integer, float[]> songPcaCoords) throws DataWriteException { try { for (Entry<Integer, float[]> e : songPcaCoords.entrySet()) { updateSongPcaCoords(e.getKey(), e.getValue()); } } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void updateSongPcaCoords(Integer songId, float[] pcaCoords) { ContentValues cv = createContentValues(); cv.put(TblSongs.SONG_PCA_X, pcaCoords[0]); cv.put(TblSongs.SONG_PCA_Y, pcaCoords[1]); update(TblSongs.TBL_NAME, cv, TblSongs.SONG_ID + "=" + songId, null); } @Override public void updateMapAlbumsPcaCoords(Collection<MapAlbum> mapAlbums) throws DataWriteException { try { for (MapAlbum album : mapAlbums) { updateMapAlbumPcaCoords(album); } } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void updateMapAlbumPcaCoords(MapAlbum album) { ContentValues cv = createContentValues(); cv.put(TblAlbums.PCA_COORDS_X, album.getGridCoords()[0]); cv.put(TblAlbums.PCA_COORDS_Y, album.getGridCoords()[1]); update(TblAlbums.TBL_NAME, cv, TblAlbums.ALBUM_ID + "=" + album.getId(), null); // Log.v(TAG, "grid coords of album " + album.getName() + "(" // + album.getId() + ") updated."); } @Override public void setRelevantTags(Collection<CompleteTag> relevantTags) throws DataWriteException { try { beginTransaction(); ContentValues cv = createContentValues(); cv.put(TblTags.IS_RELEVANT, 0); update(TblTags.TBL_NAME, cv, "", null); for (CompleteTag tag : relevantTags) { cv = createContentValues(); cv.put(TblTags.IS_RELEVANT, 1); cv.put(TblTags.MEAN_PCA_SPACE_X, tag.getMeanPcaSpaceX()); cv.put(TblTags.MEAN_PCA_SPACE_Y, tag.getMeanPcaSpaceY()); cv.put(TblTags.MEAN_PLSA_PROB, tag.getMeanPlsaProb()); cv.put(TblTags.VARIANCE_PCA_SPACE, tag.getVariancePcaSpace()); cv.put(TblTags.VARIANCE_PLSA_PROB, tag.getVariancePlsaProb()); cv.put(TblTags.IS_MAP_TAG, tag.isMapTag()); update(TblTags.TBL_NAME, cv, TblTags.TAG_ID + "=" + tag.getId(), null); // Log.v(TAG, "updating tag: " + tag.getName() + "; x: " // + tag.getMeanPcaSpaceX() + ", y: " + tag.getMeanPcaSpaceY() // + ", isMapTag: " + tag.isMapTag()); } setTransactionSuccessful(); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } finally { endTransaction(); } } @Override public int getMaximumValue(String tblName, String columnName) throws DataUnavailableException { ICursor cur = null; try { String sql = "SELECT MAX(" + columnName + ") FROM " + tblName; cur = execSelect(sql, new String[0]); if (cur == null) { throw new DataUnavailableException(); } cur.moveToNext(); return cur.getInt(0); } finally { if (cur != null) { cur.close(); } } } @Override public void setCollectionProperties(CollectionProperties properties) throws DataWriteException { beginTransaction(); try { if (properties.hasAverageSongDistance()) { setKeyValue(NAMESPACE_COLLECTION_PROPERTIES, CP_AVG_SONG_DISTANCE, properties.getAverageSongDistanceSafe()); } if (properties.hasSongDistanceStdDeviation()) { setKeyValue(NAMESPACE_COLLECTION_PROPERTIES, CP_SONG_DISTANCE_STD_DEVIATION, properties.getSongDistanceStdDeviationSafe()); } setTransactionSuccessful(); } finally { endTransaction(); } } @Override public void setKeyValue(String namespace, String key, String value) throws DataWriteException { try { ContentValues cv = createContentValues(); cv.put(TblKeyValue.VALUE, value); try { getKeyValue(namespace, key); String where = "(" + TblKeyValue.NAMESPACE + " = ?) AND (" + TblKeyValue.KEY + " = ?)"; update(TblKeyValue.TBL_NAME, cv, where, new String[] { namespace, key }); } catch (DataUnavailableException e) { cv.put(TblKeyValue.NAMESPACE, namespace); cv.put(TblKeyValue.KEY, key); insertOrThrow(TblKeyValue.TBL_NAME, cv); } } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void setKeyValue(String namespace, String key, int value) throws DataWriteException { setKeyValue(namespace, key, Integer.toString(value)); } @Override public void setKeyValue(String namespace, String key, double value) throws DataWriteException { setKeyValue(namespace, key, Double.toString(value)); } // ---------------------------------------------------------------------------------------- // DELETES // ---------------------------------------------------------------------------------------- @Override public void removeSongById(int jukefoxId) throws DataWriteException { try { // Log.v(TAG, "removeSongById: id: " + jukefoxId); String sql = "DELETE FROM " + TblSongs.TBL_NAME + " WHERE " + TblSongs.SONG_ID + " = " + jukefoxId; execSQL(sql); sql = "DELETE FROM " + TblSongGenres.TABLE_NAME + " WHERE " + TblSongs.SONG_ID + " = " + jukefoxId; execSQL(sql); removeSongCoordsById(jukefoxId); // TODO: always necessary? } catch (UncheckedSqlException e) { Log.w(TAG, e); throw new DataWriteException(e); } } private void removeSongCoordsById(int jukefoxId) { String sql = "DELETE FROM " + TblSongCoords.TBL_NAME + " WHERE " + TblSongCoords.SONG_ID + " = " + jukefoxId; execSQL(sql); } @Override public void removeUnusedAlbums() throws DataWriteException { try { removeUnusedAlbumArt(); String sql = "DELETE FROM " + TblAlbums.TBL_NAME + " WHERE " + TblAlbums.ALBUM_ID + " IN(SELECT " + TblAlbums.TBL_NAME + "." + TblAlbums.ALBUM_ID + " FROM " + TblAlbums.TBL_NAME + " LEFT JOIN " + TblSongs.TBL_NAME + " ON " + TblAlbums.TBL_NAME + "." + TblAlbums.ALBUM_ID + " = " + TblSongs.TBL_NAME + "." + TblSongs.ALBUM_ID + " WHERE " + TblSongs.TBL_NAME + "." + TblSongs.SONG_ID + " IS NULL)"; // Log.v(TAG, "sql: " + sql); long startTime = System.currentTimeMillis(); execSQL(sql); long endTime = System.currentTimeMillis(); Log.v(TAG, "remove unused albums: time: " + (endTime - startTime)); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void removeUnusedAlbumArt() { ICursor cur = null; try { String sql = "SELECT " + TblAlbums.TBL_NAME + "." + TblAlbums.LOW_RES_COVER_PATH + ", " + TblAlbums.TBL_NAME + "." + TblAlbums.HIGH_RES_COVER_PATH + " FROM " + TblAlbums.TBL_NAME + " LEFT JOIN " + TblSongs.TBL_NAME + " ON " + TblAlbums.TBL_NAME + "." + TblAlbums.ALBUM_ID + " = " + TblSongs.TBL_NAME + "." + TblSongs.ALBUM_ID + " WHERE " + TblSongs.TBL_NAME + "." + TblSongs.SONG_ID + " IS NULL"; cur = execSelect(sql, new String[] {}); if (cur.moveToNext()) { do { String path = cur.getString(0); if (path != null && path.startsWith(directoryManager.getAlbumCoverDirectory() .getAbsolutePath())) { File f = new File(path); if (!f.delete()) { Log.w(TAG, "Could not delete file: " + f.getAbsolutePath()); } } path = cur.getString(1); if (path != null && path.startsWith(directoryManager.getAlbumCoverDirectory() .getAbsolutePath())) { File f = new File(path); if (!f.delete()) { Log.w(TAG, "Could not delete file: " + f.getAbsolutePath()); } } } while (cur.moveToNext()); } } finally { if (cur != null) { cur.close(); } } } @Override public void removeObsoleteGenres() throws DataWriteException { try { Log.v(TAG, "Remove obsolete genres"); String sql = "DELETE FROM " + TblGenres.TBL_NAME + " WHERE " + TblGenres.GENRE_ID + " IN (SELECT ge." + TblGenres.GENRE_ID + " FROM " + TblGenres.TBL_NAME + " AS ge LEFT JOIN " + TblSongGenres.TABLE_NAME + " AS sg ON ge." + TblGenres.GENRE_ID + "=sg." + TblSongGenres.GENRE_ID + " WHERE sg." + TblSongGenres.GENRE_ID + " IS NULL)"; Log.v(TAG, sql); execSQL(sql); } catch (UncheckedSqlException e) { Log.w(TAG, e); throw new DataWriteException(e); } } @Override public void deleteGenreSongMapping(int genreId, int songId) throws DataWriteException { try { String where = TblSongGenres.GENRE_ID + "=? AND " + TblSongGenres.SONG_ID + "=?"; delete(TblSongGenres.TABLE_NAME, where, new String[] { "" + genreId, "" + songId }); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void emptyArtistsTable() throws DataWriteException { try { emptyTable(TblArtists.TBL_NAME, false); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void emptyTagsTable() throws DataWriteException { try { emptyTable(TblTags.TBL_NAME, false); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void deleteTagTable() throws DataWriteException { try { emptyTable(TblTags.TBL_NAME, true); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } @Override public void emptyArtistCoordsTable() throws DataWriteException { try { emptyTable(TblArtistCoords.TBL_NAME, false); } catch (UncheckedSqlException e) { throw new DataWriteException(e); } } private void emptyTable(String tblName, boolean resetAutoInc) { String sql = "DELETE FROM " + tblName; execSQL(sql); if (resetAutoInc) { resetAutoIncrement(tblName); } } private void resetAutoIncrement(String tblName) { String sql = "UPDATE SQLITE_SEQUENCE SET seq = 0 WHERE name = '" + tblName + "'"; execSQL(sql); } private void fillCoordsContentValues(ContentValues cv, String coordPrefix, float[] coords) { for (int i = 0; i < Constants.DIM; i++) { cv.put(coordPrefix + i, coords[i]); } } // ---------------------------------------------------------------------------------------- // TABLE DROP/CREATE/UPDATE FUNCTIONS // ---------------------------------------------------------------------------------------- protected void onCreate() { try { Log.i(TAG, "onCreate called."); execSQL(TblSongs.getCreateSql()); execSQL(TblArtists.getCreateSql()); execSQL(TblAlbums.getCreateSql()); execSQL(TblGenres.getCreateSql()); execSQL(TblTags.getCreateSql()); execSQL(TblArtistSets.getCreateSql()); execSQL(TblSongGenres.getCreateSql()); execSQL(TblSongCoords.getCreateSql()); execSQL(TblArtistCoords.getCreateSql()); execSQL(TblPlayLog.getCreateSql()); } catch (Exception e) { Log.w(TAG, e); Log.v(TAG, "db creation failed, deleting all tables"); try { dropRegularTables(); } catch (Exception e2) { Log.w(TAG, e2); } } } /** * Takes care of updating the database to the newest version. If the database is new or cleared, call this method * with oldVersion set to 0. * * @param oldVersion */ protected void onUpgrade(int oldVersion) { Log.v(TAG, "onUpgrade()"); boolean successful = false; beginTransaction(); try { if (oldVersion < 3) { dropRegularTables(); // Remove all tables onCreate(); // Recreate them } if (oldVersion < 4) { createMissingTables4_before(); adaptTblSongs4(); adaptTblPlayLog4(); } if (oldVersion < 5) { adaptTblPlayLog5(); createMissingTables5_after(); } if (oldVersion < 6) { adaptTblSongs6(); adaptViewRating6(); adaptArtistCoords6(); } if (oldVersion < 7) { createTblKeyValue7(); createTblRating7(); createRatingsFromPlayLog7(); } if (oldVersion < 8) { createTblLogEntry8(); } if (oldVersion < 9) { adaptTblRating9(); dropViewRating9(); } if (oldVersion < 10) { removeWrongRatingAndPlayLogEntries10(); } setTransactionSuccessful(); successful = true; } catch (UncheckedSqlException e) { Log.w(TAG, "Failed to update the database!"); Log.w(TAG, e); // TODO: set the db-version to oldVersion, since the db-update did not take effect } finally { endTransaction(); } if (!successful) { Log.w(TAG, "Recreating the database..."); resetDatabase(); } } private void createMissingTables4_before() { Log.v(TAG, "creating missing tables..."); execSQL(TblPlayerModel.getCreateSql()); execSQL(TblSongStatistics.getCreateSql()); } private void adaptTblPlayLog4() { Log.v(TAG, "adapting TblPlayLog ..."); for (String sql : TblPlayLog.getConvertTblPlayLogQueries4()) { execSQL(sql); } } private void adaptTblSongs4() { Log.v(TAG, "adapting TblSongs ..."); for (String sql : TblSongs.getConvertTblSongsQueries4()) { execSQL(sql); } } @SuppressWarnings("deprecation") private void createMissingTables5_after() { Log.v(TAG, "creating missing tables..."); execSQL(ViewRating.getCreateSql()); } private void adaptTblPlayLog5() { Log.v(TAG, "adapting TblPlayLog..."); for (String sql : TblPlayLog.getConvertTblPlayLogQueries5()) { execSQL(sql); } } private void adaptTblSongs6() { Log.v(TAG, "adapting TblSongs..."); for (String sql : TblSongs.getConvertTblSongsQueries6()) { execSQL(sql); } } @SuppressWarnings("deprecation") private void adaptViewRating6() { Log.v(TAG, "adapting ViewRating..."); for (String sql : ViewRating.getUpdateTo6()) { execSQL(sql); } } private void adaptArtistCoords6() { Log.v(TAG, "adapting TblArtistCoords ..."); execSQL(TblArtistCoords.getAddMeArtistIdColumnString()); replaceArtistIds6(); for (String sql : TblArtistCoords.getRemoveArtistIdColumnString()) { execSQL(sql); } Log.v(TAG, "adapted TblArtistCoords ..."); } private void replaceArtistIds6() { try { Log.v(TAG, "exchanging TblArtistCoords ..."); ICursor cur = null; try { String sql = "SELECT " + TblArtists.ARTIST_ID + "," + TblArtists.ME_ARTIST_ID + " FROM " + TblArtists.TBL_NAME; cur = execSelect(sql, new String[] {}); List<Pair<Integer, Integer>> artists = new LinkedList<Pair<Integer, Integer>>(); while (cur.moveToNext()) { artists.add(new Pair<Integer, Integer>(cur.getInt(0), cur.getInt(1))); } if (cur != null) { cur.close(); cur = null; } beginTransaction(); try { for (Pair<Integer, Integer> artist : artists) { ContentValues cv = createContentValues(); cv.put(TblArtistCoords.ME_ARTIST_ID, artist.second); update(TblArtists.TBL_NAME, cv, TblArtistCoords.ARTIST_ID + " = " + artist.first, null); } setTransactionSuccessful(); } finally { endTransaction(); } } finally { if (cur != null) { cur.close(); } } Log.v(TAG, "exchanging TblArtistCoords ..."); } catch (UncheckedSqlException e) { Log.w(TAG, e); } } private void createTblKeyValue7() { Log.v(TAG, "Creating table " + TblKeyValue.TBL_NAME + " ..."); execSQL(TblKeyValue.getCreateSql()); } private void createTblRating7() { Log.v(TAG, "Creating table " + TblRating.TBL_NAME + " ..."); execSQL(TblRating.getCreateSql7()); } private void createRatingsFromPlayLog7() { Log.v(TAG, "Importing ratings from playlog ..."); List<String> sqls = TblRating.getCreateRatingsFromPlayLog7Sql(); for (String sql : sqls) { execSQL(sql); } } private void createTblLogEntry8() { Log.v(TAG, "Creating table " + TblLogEntry.TBL_NAME + " ..."); execSQL(TblLogEntry.getCreateSql8()); } private void adaptTblRating9() { Log.v(TAG, "adapting TblRating ..."); List<String> sqls = TblRating.getUpdateTo9(); for (String sql : sqls) { execSQL(sql); } } @SuppressWarnings("deprecation") private void dropViewRating9() { Log.v(TAG, "Dropping ViewRating ..."); List<String> sqls = ViewRating.getDrop9(); for (String sql : sqls) { execSQL(sql); } } /** * There was a bug before v10, that fake playlog- and rating data was written async in * {@link NextSongCalculationThread}. This led to entries in the db that survived. We remove them here. */ private void removeWrongRatingAndPlayLogEntries10() { // Remove the playlog entries execSQL("DELETE FROM tblPlayLog " + "WHERE (playLogId IN (" + " SELECT DISTINCT playLogId " + " FROM tblPlayLog AS p " + " JOIN tblRating AS r ON (r.timestamp = p.timestamp) " + " WHERE (CAST ((r.rating * 1000) AS INT) / 1000.0 = 0.639) " + // positive prediction, cut after 3 decimal places " OR (CAST ((r.rating * 1000) AS INT) / 1000.0 = -0.680)" + // negative prediction, cut after 3 decimal places "));"); // Remove the rating entries execSQL("DELETE FROM tblRating " + "WHERE (CAST ((rating * 1000) AS INT) / 1000.0 = 0.639) " + // positive prediction, cut after 3 decimal places " OR (CAST ((rating * 1000) AS INT) / 1000.0 = -0.680)"); // negative prediction, cut after 3 decimal places } }