/* == This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2015, Enno Gottschalk <mrmaffen@googlemail.com>
*
* Tomahawk 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
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
package org.tomahawk.libtomahawk.database;
import org.tomahawk.libtomahawk.collection.Artist;
import org.tomahawk.libtomahawk.collection.CollectionManager;
import org.tomahawk.libtomahawk.collection.DbCollection;
import org.tomahawk.libtomahawk.resolver.FuzzyIndex;
import org.tomahawk.libtomahawk.resolver.models.ScriptResolverTrack;
import org.tomahawk.libtomahawk.utils.StringUtils;
import org.tomahawk.tomahawk_android.utils.PreferenceUtils;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CollectionDb extends SQLiteOpenHelper {
public static final String TAG = CollectionDb.class.getSimpleName();
public static final String ID = "_id";
public static final String TABLE_ARTISTS = "artists";
public static final String ARTISTS_ARTIST = "artist";
public static final String ARTISTS_ARTISTDISAMBIGUATION = "artistDisambiguation";
public static final String ARTISTS_LASTMODIFIED = "artistLastModified";
public static final String ARTISTS_TYPE = "artistType";
public static final String TABLE_ALBUMARTISTS = "albumArtists";
public static final String ALBUMARTISTS_ALBUMARTIST = "albumArtist";
public static final String ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION = "albumArtistDisambiguation";
public static final String ALBUMARTISTS_LASTMODIFIED = "albumArtistLastModified";
public static final String TABLE_ALBUMS = "albums";
public static final String ALBUMS_ALBUM = "album";
public static final String ALBUMS_IMAGEPATH = "imagePath";
public static final String ALBUMS_ALBUMARTISTID = "albumArtistId";
public static final String ALBUMS_LASTMODIFIED = "albumLastModified";
public static final String ALBUMS_TYPE = "albumType";
public static final String TABLE_ARTISTALBUMS = "artistAlbums";
public static final String ARTISTALBUMS_ALBUMID = "albumId";
public static final String ARTISTALBUMS_ARTISTID = "artistId";
public static final String TABLE_TRACKS = "tracks";
public static final String TRACKS_TRACK = "track";
public static final String TRACKS_ARTISTID = "artistId";
public static final String TRACKS_ALBUMID = "albumId";
public static final String TRACKS_URL = "url";
public static final String TRACKS_DURATION = "duration";
public static final String TRACKS_ALBUMPOS = "albumPos";
public static final String TRACKS_LINKURL = "linkUrl";
public static final String TRACKS_LASTMODIFIED = "trackLastModified";
public static final String TABLE_REVISIONHISTORY = "revisionHistory";
public static final String REVISIONHISTORY_ACTION = "action";
public static final String REVISIONHISTORY_TRACKCOUNT = "trackCount";
public static final String REVISIONHISTORY_REVISION = "revision";
public static final String REVISIONHISTORY_TIMESTAMP = "timeStamp";
protected static final int ACTION_WIPE = 0;
protected static final int ACTION_ADDTRACKS = 1;
protected static final int TYPE_DEFAULT = 0;
// This type marks an entry that has been explicitly loved.
// E.g. an artist that has been loved by the user.
protected static final int TYPE_HATCHET_EXPLICIT = 1;
// This type marks an entry that has not been explicitly loved by the user but has been added to
// the collection because it was necessary in order to love another entry. E.g. an artist that
// was added implicitly because an album has been loved by the user.
protected static final int TYPE_HATCHET_IMPLICIT = 2;
private static final String CREATE_TABLE_ARTISTS = "CREATE TABLE IF NOT EXISTS "
+ TABLE_ARTISTS + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ ARTISTS_ARTIST + " TEXT,"
+ ARTISTS_ARTISTDISAMBIGUATION + " TEXT,"
+ ARTISTS_LASTMODIFIED + " INTEGER,"
+ ARTISTS_TYPE + " INTEGER,"
+ "UNIQUE (" + ARTISTS_ARTIST + ", " + ARTISTS_ARTISTDISAMBIGUATION + ", "
+ ARTISTS_TYPE
+ ") ON CONFLICT IGNORE);";
private static final String CREATE_TABLE_ALBUMARTISTS = "CREATE TABLE IF NOT EXISTS "
+ TABLE_ALBUMARTISTS + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ ALBUMARTISTS_ALBUMARTIST + " TEXT,"
+ ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION + " TEXT,"
+ ALBUMARTISTS_LASTMODIFIED + " INTEGER,"
+ "UNIQUE (" + ALBUMARTISTS_ALBUMARTIST + ", " + ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION
+ ") ON CONFLICT IGNORE);";
private static final String CREATE_TABLE_ALBUMS = "CREATE TABLE IF NOT EXISTS "
+ TABLE_ALBUMS + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ ALBUMS_ALBUM + " TEXT,"
+ ALBUMS_ALBUMARTISTID + " INTEGER,"
+ ALBUMS_IMAGEPATH + " TEXT,"
+ ALBUMS_LASTMODIFIED + " INTEGER,"
+ ALBUMS_TYPE + " INTEGER,"
+ "UNIQUE (" + ALBUMS_ALBUM + ", " + ALBUMS_ALBUMARTISTID + ", " + ALBUMS_TYPE
+ ") ON CONFLICT IGNORE,"
+ "FOREIGN KEY(" + ALBUMS_ALBUMARTISTID + ") REFERENCES "
+ TABLE_ALBUMARTISTS + "(" + ID + "));";
private static final String CREATE_TABLE_ARTISTALBUMS = "CREATE TABLE IF NOT EXISTS "
+ TABLE_ARTISTALBUMS + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ ARTISTALBUMS_ALBUMID + " INTEGER,"
+ ARTISTALBUMS_ARTISTID + " INTEGER,"
+ "UNIQUE (" + ARTISTALBUMS_ALBUMID + ", " + ARTISTALBUMS_ARTISTID
+ ") ON CONFLICT IGNORE,"
+ "FOREIGN KEY(" + ARTISTALBUMS_ALBUMID + ") REFERENCES "
+ TABLE_ALBUMS + "(" + ID + "),"
+ "FOREIGN KEY(" + ARTISTALBUMS_ARTISTID + ") REFERENCES "
+ TABLE_ARTISTS + "(" + ID + "));";
private static final String CREATE_TABLE_TRACKS = "CREATE TABLE IF NOT EXISTS "
+ TABLE_TRACKS + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ TRACKS_TRACK + " TEXT,"
+ TRACKS_ARTISTID + " INTEGER,"
+ TRACKS_ALBUMID + " INTEGER,"
+ TRACKS_URL + " TEXT,"
+ TRACKS_DURATION + " INTEGER,"
+ TRACKS_ALBUMPOS + " INTEGER,"
+ TRACKS_LINKURL + " TEXT,"
+ TRACKS_LASTMODIFIED + " INTEGER,"
+ "UNIQUE (" + TRACKS_TRACK + ", " + TRACKS_ARTISTID + ", " + TRACKS_ALBUMID
+ ") ON CONFLICT IGNORE,"
+ "FOREIGN KEY(" + TRACKS_ARTISTID + ") REFERENCES "
+ TABLE_ARTISTS + "(" + ID + "),"
+ "FOREIGN KEY(" + TRACKS_ALBUMID + ") REFERENCES "
+ TABLE_ALBUMS + "(" + ID + "));";
private static final String CREATE_TABLE_REVISIONHISTORY = "CREATE TABLE IF NOT EXISTS "
+ TABLE_REVISIONHISTORY + " ("
+ ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ REVISIONHISTORY_ACTION + " INTEGER,"
+ REVISIONHISTORY_TRACKCOUNT + " INTEGER,"
+ REVISIONHISTORY_REVISION + " TEXT,"
+ REVISIONHISTORY_TIMESTAMP + " INTEGER );";
private static final int DB_VERSION = 5;
private static final String DB_FILE_SUFFIX = "_collection.db";
protected final SQLiteDatabase mDb;
private static final String LAST_COLLECTION_DB_UPDATE_SUFFIX = "_last_collection_db_update";
public static class WhereInfo {
public String connection;
public Map<String, String[]> where = new HashMap<>();
public boolean equals = true;
}
private static class JoinInfo {
String table;
Map<String, String> conditions = new HashMap<>();
}
private String mCollectionId;
private FuzzyIndex mFuzzyIndex;
public CollectionDb(Context context, String collectionId) {
super(context, collectionId + DB_FILE_SUFFIX, null, DB_VERSION);
Log.d(TAG, "Constructed CollectionDb '" + collectionId + DB_FILE_SUFFIX + "' with version "
+ DB_VERSION + ", objectId: " + this.hashCode());
mCollectionId = collectionId;
close();
mDb = getWritableDatabase();
mFuzzyIndex = new FuzzyIndex(this);
}
public String getCollectionId() {
return mCollectionId;
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "onCreate - CollectionDb '" + db.getPath() + "' with version "
+ db.getVersion() + ", objectId: " + this.hashCode());
db.execSQL(CREATE_TABLE_ARTISTS);
db.execSQL(CREATE_TABLE_ALBUMARTISTS);
db.execSQL(CREATE_TABLE_ALBUMS);
db.execSQL(CREATE_TABLE_ARTISTALBUMS);
db.execSQL(CREATE_TABLE_TRACKS);
db.execSQL(CREATE_TABLE_REVISIONHISTORY);
Log.d(TAG, "onCreate finished - CollectionDb '" + db.getPath() + "' with version "
+ db.getVersion() + ", objectId: " + this.hashCode());
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion
+ ", which might destroy all old data");
if (oldVersion < 4) {
wipe(db);
}
if (oldVersion < 5) {
db.execSQL(CREATE_TABLE_REVISIONHISTORY);
long lastDbUpdate =
PreferenceUtils.getLong(mCollectionId + LAST_COLLECTION_DB_UPDATE_SUFFIX);
if (lastDbUpdate > 0) {
storeNewRevision(db, String.valueOf(lastDbUpdate), ACTION_ADDTRACKS);
}
}
}
public synchronized void addTracks(List<ScriptResolverTrack> tracks) {
long time = System.currentTimeMillis();
// Check if we want to store the album as a compilation album (with artist "Various Artists")
Map<String, Set<String>> albumArtists = new HashMap<>();
for (ScriptResolverTrack track : tracks) {
if (track.artist == null) {
track.artist = "";
}
if (track.artistDisambiguation == null) {
track.artistDisambiguation = "";
}
if (track.album == null) {
track.album = "";
}
if (track.albumArtist == null) {
track.albumArtist = "";
}
if (track.albumArtistDisambiguation == null) {
track.albumArtistDisambiguation = "";
}
if (track.track == null) {
track.track = "";
}
Set<String> artists = albumArtists.get(track.album + "♠" + track.albumArtist);
if (artists == null) {
artists = new HashSet<>();
albumArtists.put(track.album + "♠" + track.albumArtist, artists);
}
if (artists.size() < 2) {
artists.add(track.artist);
}
}
Map<String, Long> artistLastModifiedMap = new HashMap<>();
// First we insert all artists and albumArtists
mDb.beginTransaction();
for (ScriptResolverTrack track : tracks) {
if (albumArtists.get(track.album + "♠" + track.albumArtist).size() > 1) {
ContentValues values = new ContentValues();
values.put(ARTISTS_ARTIST, Artist.COMPILATION_ARTIST.getName());
values.put(ARTISTS_ARTISTDISAMBIGUATION, "");
String artistKey = Artist.COMPILATION_ARTIST.getName() + "♠" + "";
Long lastModified = artistLastModifiedMap.get(artistKey);
if (lastModified == null || lastModified < track.lastModified) {
artistLastModifiedMap.put(artistKey, track.lastModified);
lastModified = track.lastModified;
}
values.put(ARTISTS_LASTMODIFIED, lastModified);
values.put(ARTISTS_TYPE, TYPE_DEFAULT);
mDb.insert(TABLE_ARTISTS, null, values);
}
ContentValues values = new ContentValues();
values.put(ARTISTS_ARTIST, track.artist);
values.put(ARTISTS_ARTISTDISAMBIGUATION, track.artistDisambiguation);
String artistKey = track.artist + "♠" + track.artistDisambiguation;
Long lastModified = artistLastModifiedMap.get(artistKey);
if (lastModified == null || lastModified < track.lastModified) {
artistLastModifiedMap.put(artistKey, track.lastModified);
lastModified = track.lastModified;
}
values.put(ARTISTS_LASTMODIFIED, lastModified);
values.put(ARTISTS_TYPE, TYPE_DEFAULT);
mDb.insert(TABLE_ARTISTS, null, values);
values = new ContentValues();
values.put(ALBUMARTISTS_ALBUMARTIST, track.albumArtist);
values.put(ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION, track.albumArtistDisambiguation);
values.put(ALBUMARTISTS_LASTMODIFIED, lastModified);
mDb.insert(TABLE_ALBUMARTISTS, null, values);
}
mDb.setTransactionSuccessful();
mDb.endTransaction();
Cursor cursor = mDb.query(TABLE_ARTISTS,
new String[]{ID, ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION},
null, null, null, null, null);
Map<String, Integer> cachedArtists = cursorToMap(cursor);
Map<String, Long> albumLastModifiedMap = new HashMap<>();
mDb.beginTransaction();
for (ScriptResolverTrack track : tracks) {
ContentValues values = new ContentValues();
values.put(ALBUMS_ALBUM, track.album);
int albumArtistId;
if (albumArtists.get(track.album + "♠" + track.albumArtist).size() == 1) {
albumArtistId = cachedArtists.get(
concatKeys(track.artist, track.artistDisambiguation));
} else {
albumArtistId = cachedArtists.get(
concatKeys(Artist.COMPILATION_ARTIST.getName(), ""));
}
values.put(ALBUMS_ALBUMARTISTID, albumArtistId);
values.put(ALBUMS_IMAGEPATH, track.imagePath);
String artistKey = track.album + "♠" + albumArtistId;
Long lastModified = albumLastModifiedMap.get(artistKey);
if (lastModified == null || lastModified < track.lastModified) {
albumLastModifiedMap.put(artistKey, track.lastModified);
lastModified = track.lastModified;
}
values.put(ALBUMS_LASTMODIFIED, lastModified);
values.put(ALBUMS_TYPE, TYPE_DEFAULT);
mDb.insert(TABLE_ALBUMS, null, values);
}
mDb.setTransactionSuccessful();
mDb.endTransaction();
cursor = mDb.query(TABLE_ALBUMS,
new String[]{ID, ALBUMS_ALBUM, ALBUMS_ALBUMARTISTID},
null, null, null, null, null);
Map<String, Integer> cachedAlbums = cursorToMap(cursor);
mDb.beginTransaction();
for (ScriptResolverTrack track : tracks) {
ContentValues values = new ContentValues();
int albumArtistId;
if (albumArtists.get(track.album + "♠" + track.albumArtist).size() == 1) {
albumArtistId = cachedArtists.get(
concatKeys(track.artist, track.artistDisambiguation));
} else {
albumArtistId = cachedArtists.get(
concatKeys(Artist.COMPILATION_ARTIST.getName(), ""));
}
int artistId = cachedArtists.get(concatKeys(track.artist, track.artistDisambiguation));
int albumId = cachedAlbums.get(concatKeys(track.album, albumArtistId));
values.put(ARTISTALBUMS_ARTISTID, artistId);
values.put(ARTISTALBUMS_ALBUMID, albumId);
mDb.insert(TABLE_ARTISTALBUMS, null, values);
values = new ContentValues();
values.put(TRACKS_TRACK, track.track);
values.put(TRACKS_ARTISTID, artistId);
values.put(TRACKS_ALBUMID, albumId);
values.put(TRACKS_URL, track.url);
values.put(TRACKS_DURATION, (int) track.duration);
values.put(TRACKS_LINKURL, track.linkUrl);
values.put(TRACKS_ALBUMPOS, track.albumpos);
values.put(TRACKS_LASTMODIFIED, track.lastModified);
mDb.insert(TABLE_TRACKS, null, values);
}
mDb.setTransactionSuccessful();
mDb.endTransaction();
Log.d(TAG, "Added " + tracks.size() + " tracks in " + (System.currentTimeMillis() - time)
+ "ms");
if (tracks.size() > 0) {
storeNewRevision(String.valueOf(System.currentTimeMillis()), ACTION_ADDTRACKS);
}
mFuzzyIndex.ensureIndex();
((DbCollection) CollectionManager.get().getCollection(mCollectionId)).setInitialized(true);
}
private static Map<String, Integer> cursorToMap(Cursor cursor) {
Map<String, Integer> map = new HashMap<>();
try {
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
do {
map.put(concatKeys(cursor.getString(1), cursor.getString(2)),
cursor.getInt(0));
} while (cursor.moveToNext());
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return map;
}
public synchronized void wipe() {
wipe(mDb);
}
private void wipe(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS `" + TABLE_ARTISTS + "`;");
db.execSQL(CREATE_TABLE_ARTISTS);
db.execSQL("DROP TABLE IF EXISTS `" + TABLE_ALBUMARTISTS + "`;");
db.execSQL(CREATE_TABLE_ALBUMARTISTS);
db.execSQL("DROP TABLE IF EXISTS `" + TABLE_ALBUMS + "`;");
db.execSQL(CREATE_TABLE_ALBUMS);
db.execSQL("DROP TABLE IF EXISTS `" + TABLE_ARTISTALBUMS + "`;");
db.execSQL(CREATE_TABLE_ARTISTALBUMS);
db.execSQL("DROP TABLE IF EXISTS `" + TABLE_TRACKS + "`;");
db.execSQL(CREATE_TABLE_TRACKS);
storeNewRevision(db, String.valueOf(System.currentTimeMillis()), ACTION_WIPE);
}
/**
* Convenience method. Uses a default set of fields.
*/
public synchronized Cursor tracks(WhereInfo where, String[] orderBy) {
String[] fields = new String[]{ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION, ALBUMS_ALBUM,
TRACKS_TRACK, TRACKS_DURATION, TRACKS_URL, TRACKS_LINKURL, TRACKS_ALBUMPOS,
TRACKS_LASTMODIFIED, TRACKS_ALBUMID};
return tracks(where, orderBy, fields);
}
public synchronized Cursor tracks(WhereInfo where, String[] orderBy, String[] fields) {
List<JoinInfo> joinInfos = new ArrayList<>();
JoinInfo joinInfo = new JoinInfo();
joinInfo.table = TABLE_ARTISTS;
joinInfo.conditions.put(TABLE_TRACKS + "." + TRACKS_ARTISTID, TABLE_ARTISTS + "." + ID);
joinInfos.add(joinInfo);
joinInfo = new JoinInfo();
joinInfo.table = TABLE_ALBUMS;
joinInfo.conditions.put(TABLE_TRACKS + "." + TRACKS_ALBUMID, TABLE_ALBUMS + "." + ID);
joinInfos.add(joinInfo);
String[] groupBy = new String[]{TRACKS_TRACK, ARTISTS_ARTIST, ALBUMS_ALBUM};
return sqlSelect(TABLE_TRACKS, fields, where, joinInfos, orderBy, groupBy, null,
TRACKS_LASTMODIFIED, false);
}
public synchronized long tracksCurrentRevision() {
String[] fields = new String[]{TRACKS_LASTMODIFIED};
long currentRevision = -1;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_TRACKS, fields, null, null,
new String[]{TRACKS_LASTMODIFIED + " DESC"}, null, null, null, false);
if (cursor.moveToFirst()) {
currentRevision = cursor.getLong(0);
} else {
Log.e(TAG, "tracksCurrentRevision - no tracks in table!");
return -1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return currentRevision;
}
public synchronized Cursor albums(String[] orderBy) {
String[] fields = new String[]{ALBUMS_ALBUM, ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION,
ALBUMS_IMAGEPATH, ALBUMS_LASTMODIFIED};
List<JoinInfo> joinInfos = new ArrayList<>();
JoinInfo joinInfo = new JoinInfo();
joinInfo.table = TABLE_ARTISTS;
joinInfo.conditions.put(
TABLE_ALBUMS + "." + ALBUMS_ALBUMARTISTID, TABLE_ARTISTS + "." + ID);
joinInfos.add(joinInfo);
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ALBUMS_ALBUM, new String[]{""});
whereInfo.equals = false;
String[] groupBy = new String[]{ALBUMS_ALBUM, ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION};
return sqlSelect(TABLE_ALBUMS, fields, whereInfo, joinInfos, orderBy, groupBy, ALBUMS_TYPE,
ALBUMS_LASTMODIFIED, false);
}
public synchronized Cursor artists(String[] orderBy) {
String[] fields = new String[]{ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION,
ARTISTS_LASTMODIFIED};
JoinInfo joinInfo = new JoinInfo();
joinInfo.table = TABLE_ARTISTS;
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{Artist.COMPILATION_ARTIST.getName(), ""});
whereInfo.equals = false;
String[] groupBy = new String[]{ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION};
return sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, orderBy, groupBy, ARTISTS_TYPE,
ARTISTS_LASTMODIFIED, false);
}
public synchronized Cursor albumArtists(String[] orderBy) {
String[] fields = new String[]{ALBUMARTISTS_ALBUMARTIST,
ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION, ALBUMARTISTS_LASTMODIFIED};
String[] groupBy = new String[]{ALBUMARTISTS_ALBUMARTIST,
ALBUMARTISTS_ALBUMARTISTDISAMBIGUATION};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{Artist.COMPILATION_ARTIST.getName(), ""});
whereInfo.equals = false;
return sqlSelect(TABLE_ALBUMARTISTS, fields, whereInfo, null, orderBy, groupBy, null,
ALBUMARTISTS_LASTMODIFIED, false);
}
public synchronized long artistCurrentRevision(String artist, String artistDisambiguation) {
String[] fields = new String[]{ARTISTS_LASTMODIFIED};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{artist});
whereInfo.where.put(ARTISTS_ARTISTDISAMBIGUATION, new String[]{artistDisambiguation});
long currentRevision = -1;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, null, null, null, null,
false);
if (cursor.moveToFirst()) {
currentRevision = cursor.getLong(0);
} else {
Log.e(TAG, "artistCurrentRevision - Couldn't find artist with given name!");
return -1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return currentRevision;
}
public synchronized Cursor artistAlbums(String artist, String artistDisambiguation) {
String[] fields = new String[]{ID};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{artist});
whereInfo.where.put(ARTISTS_ARTISTDISAMBIGUATION, new String[]{artistDisambiguation});
int artistId;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, null, null, ARTISTS_TYPE,
null, true);
if (cursor.moveToFirst()) {
artistId = cursor.getInt(0);
} else {
Log.e(TAG, "artistAlbums - Couldn't find artist with given name!");
return null;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
fields = new String[]{ALBUMS_ALBUM, ARTISTS_ARTIST, ARTISTS_ARTISTDISAMBIGUATION,
ALBUMS_IMAGEPATH, ALBUMS_LASTMODIFIED};
whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTALBUMS_ARTISTID, new String[]{String.valueOf(artistId)});
List<JoinInfo> joinInfos = new ArrayList<>();
JoinInfo joinInfo = new JoinInfo();
joinInfo.table = TABLE_ALBUMS;
joinInfo.conditions.put(
TABLE_ARTISTALBUMS + "." + ARTISTALBUMS_ALBUMID, TABLE_ALBUMS + "." + ID);
joinInfos.add(joinInfo);
joinInfo = new JoinInfo();
joinInfo.table = TABLE_ARTISTS;
joinInfo.conditions.put(
TABLE_ALBUMS + "." + ALBUMS_ALBUMARTISTID, TABLE_ARTISTS + "." + ID);
joinInfos.add(joinInfo);
return sqlSelect(TABLE_ARTISTALBUMS, fields, whereInfo, joinInfos,
new String[]{ALBUMS_ALBUM}, null, ALBUMS_TYPE, null, true);
}
public synchronized long albumCurrentRevision(String album, String albumArtist,
String albumArtistDisambiguation) {
String[] fields = new String[]{ID};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{albumArtist});
whereInfo.where.put(ARTISTS_ARTISTDISAMBIGUATION, new String[]{albumArtistDisambiguation});
int artistId;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, null, null, null, null,
false);
if (cursor.moveToFirst()) {
artistId = cursor.getInt(0);
} else {
Log.e(TAG, "albumCurrentRevision - Couldn't find artist with given name!");
return -1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
fields = new String[]{ALBUMS_LASTMODIFIED};
whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ALBUMS_ALBUM, new String[]{album});
whereInfo.where.put(ALBUMS_ALBUMARTISTID, new String[]{String.valueOf(artistId)});
long currentRevision = -1;
cursor = null;
try {
cursor = sqlSelect(TABLE_ALBUMS, fields, whereInfo, null, null, null, null, null,
false);
if (cursor.moveToFirst()) {
currentRevision = cursor.getLong(0);
} else {
Log.e(TAG, "albumCurrentRevision - Couldn't find album with given name!");
return -1;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return currentRevision;
}
public synchronized Cursor albumTracks(String album, String albumArtist,
String albumArtistDisambiguation) {
String[] fields = new String[]{ID};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{albumArtist});
whereInfo.where.put(ARTISTS_ARTISTDISAMBIGUATION, new String[]{albumArtistDisambiguation});
int artistId;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, null, null, ARTISTS_TYPE,
null, true);
if (cursor.moveToFirst()) {
artistId = cursor.getInt(0);
} else {
Log.e(TAG, "albumTracks - Couldn't find artist with given name!");
return null;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
fields = new String[]{ID};
whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ALBUMS_ALBUM, new String[]{album});
whereInfo.where.put(ALBUMS_ALBUMARTISTID, new String[]{String.valueOf(artistId)});
int albumId;
cursor = null;
try {
cursor = sqlSelect(TABLE_ALBUMS, fields, whereInfo, null, null, null, ALBUMS_TYPE,
null, true);
if (cursor.moveToFirst()) {
albumId = cursor.getInt(0);
} else {
Log.e(TAG, "albumTracks - Couldn't find album with given name!");
return null;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(TRACKS_ALBUMID, new String[]{String.valueOf(albumId)});
return tracks(whereInfo, new String[]{TRACKS_ALBUMPOS});
}
public synchronized Cursor artistTracks(String artist, String artistDisambiguation) {
String[] fields = new String[]{ID};
WhereInfo whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(ARTISTS_ARTIST, new String[]{artist});
whereInfo.where.put(ARTISTS_ARTISTDISAMBIGUATION, new String[]{artistDisambiguation});
int artistId;
Cursor cursor = null;
try {
cursor = sqlSelect(TABLE_ARTISTS, fields, whereInfo, null, null, null, ARTISTS_TYPE,
null, true);
if (cursor.moveToFirst()) {
artistId = cursor.getInt(0);
} else {
Log.e(TAG, "artistTracks - Couldn't find artist with given name!");
return null;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
whereInfo = new WhereInfo();
whereInfo.connection = "AND";
whereInfo.where.put(TRACKS_ARTISTID, new String[]{String.valueOf(artistId)});
return tracks(whereInfo, new String[]{TRACKS_ALBUMID});
}
private Cursor sqlSelect(String table, String[] fields, WhereInfo where,
List<JoinInfo> joinInfos, String[] orderBy, String[] groupBy, String typeColumn,
String lastModifiedColumn, boolean filterAllLoved) {
String whereString = "";
List<String> allWhereValues = new ArrayList<>();
if (where != null) {
whereString = " WHERE ";
boolean notFirst = false;
for (String whereKey : where.where.keySet()) {
String[] whereValues = where.where.get(whereKey);
for (String whereValue : whereValues) {
if (notFirst) {
whereString += " " + where.connection + " ";
}
notFirst = true;
whereString += table + "." + whereKey + (where.equals ? " = " : " != ") + "?";
allWhereValues.add(whereValue);
}
}
}
if (typeColumn != null) {
if (whereString.isEmpty()) {
whereString = " WHERE ";
} else {
whereString += " AND ";
}
// filter out all implicitly added items
whereString += typeColumn + " != ?";
allWhereValues.add(String.valueOf(TYPE_HATCHET_IMPLICIT));
}
if (filterAllLoved) {
if (whereString.isEmpty()) {
whereString = " WHERE ";
} else {
whereString += " AND ";
}
// filter out all explicitly added items
whereString += typeColumn + " != ?";
allWhereValues.add(String.valueOf(TYPE_HATCHET_EXPLICIT));
}
String joinString = "";
if (joinInfos != null) {
for (JoinInfo joinInfo : joinInfos) {
joinString += " INNER JOIN " + joinInfo.table + " ON ";
boolean notFirst = false;
for (String joinKey : joinInfo.conditions.keySet()) {
if (notFirst) {
joinString += " AND ";
}
notFirst = true;
joinString += joinKey + " = " + joinInfo.conditions.get(joinKey);
}
}
}
String orderString = StringUtils.join(" , ", orderBy);
if (orderBy != null) {
orderString = " ORDER BY " + orderString;
} else {
orderString = "";
}
String deduplicationOrderString = "";
String groupString = StringUtils.join(" , ", groupBy);
if (groupBy != null) {
groupString = " GROUP BY " + groupString;
if (lastModifiedColumn != null) {
deduplicationOrderString = " ORDER BY " + lastModifiedColumn;
}
} else {
groupString = "";
}
String fieldsString = StringUtils.join(", ", fields);
if (fields == null) {
fieldsString = "*";
}
String statement = "SELECT * FROM ( SELECT " + fieldsString + " FROM " + table + joinString
+ whereString + deduplicationOrderString + " ) " + groupString + orderString;
String[] allWhereValuesArray = null;
if (allWhereValues.size() > 0) {
allWhereValuesArray = allWhereValues.toArray(new String[allWhereValues.size()]);
}
return mDb.rawQuery(statement, allWhereValuesArray);
}
private void storeNewRevision(String revision, int action) {
storeNewRevision(mDb, revision, action);
}
private static void storeNewRevision(SQLiteDatabase db, String revision, int action) {
Cursor cursor = db.query(TABLE_TRACKS, new String[]{ID}, null, null, null, null, null);
int trackCount = cursor.getCount();
cursor.close();
db.beginTransaction();
ContentValues values = new ContentValues();
values.put(REVISIONHISTORY_ACTION, action);
values.put(REVISIONHISTORY_TRACKCOUNT, trackCount);
values.put(REVISIONHISTORY_REVISION, revision);
values.put(REVISIONHISTORY_TIMESTAMP, System.currentTimeMillis());
db.insert(TABLE_REVISIONHISTORY, null, values);
db.setTransactionSuccessful();
db.endTransaction();
}
public String getRevision() {
Cursor cursor = null;
try {
cursor = mDb.query(TABLE_REVISIONHISTORY, new String[]{REVISIONHISTORY_REVISION},
null, null, null, null, REVISIONHISTORY_TIMESTAMP + " DESC", "1");
if (!cursor.moveToFirst()) {
return null;
}
return cursor.getString(0);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public long getLastUpdated() {
Cursor cursor = null;
try {
cursor = mDb.query(TABLE_REVISIONHISTORY, new String[]{REVISIONHISTORY_TIMESTAMP},
null, null, null, null, REVISIONHISTORY_TIMESTAMP + " DESC", "1");
if (!cursor.moveToFirst()) {
return -1;
}
return cursor.getLong(0);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private static String concatKeys(Object... keys) {
String result = "";
for (int i = 0; i < keys.length; i++) {
if (i > 0) {
result += "♣";
}
result += keys[i];
}
return result;
}
public FuzzyIndex getFuzzyIndex() {
return mFuzzyIndex;
}
}