/* * Copyright (C) 2014 Fastboot Mobile, LLC. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, see <http://www.gnu.org/licenses>. */ package com.fastbootmobile.encore.providers; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.RemoteException; import android.util.Log; import com.fastbootmobile.encore.framework.PluginsLookup; import com.fastbootmobile.encore.model.Playlist; import com.fastbootmobile.encore.model.SearchResult; import com.fastbootmobile.encore.model.Song; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; /** * Created by h4o on 01/07/2014. */ public class MultiProviderDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "MultiProviderDBHelper"; private static final int DATABASE_VERSION = 2; private static final String DATABASE_NAME = "multiprovider_playlists"; private static final String TABLE_PLAYLIST = "playlist"; private static final String TABLE_SONGS = "song"; private static final String KEY_ID = "id"; private static final String KEY_PLAYLIST_NAME = "playlist_name"; private static final String KEY_SONG_REF = "song_ref"; private static final String KEY_PLAYLIST_ID = "playlist_id"; private static final String KEY_PACKAGE_NAME = "package_name"; private static final String KEY_SERVICE = "service"; private static final String KEY_PROVIDER = "provider"; private static final String KEY_POSITION = "position"; private static final String CREATE_TABLE_PLAYLIST = "CREATE TABLE IF NOT EXISTS " + TABLE_PLAYLIST + "(" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_PLAYLIST_NAME + " TEXT)"; private static final String CREATE_TABLE_SONGS = "CREATE TABLE IF NOT EXISTS " + TABLE_SONGS + "(" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_PLAYLIST_ID + " INTEGER," + KEY_SONG_REF + " TEXT," + KEY_PACKAGE_NAME + " TEXT," + KEY_SERVICE + " TEXT," + KEY_POSITION + " INTEGER," + KEY_PROVIDER + " TEXT)"; private HashMap<String, Long> mPlayListRefID; private HashMap<String, Long> mSongRefID; private HashMap<String, Playlist> mPlaylists; private HashMap<String, ProviderIdentifier> mRefProviderId; private SQLiteDatabase mDatabase; private LocalCallback mCallback; private SearchResult mSearchResult; private ProviderIdentifier mProviderIdentifier; private boolean mFetched; public interface LocalCallback { void playlistUpdated(final Playlist playlist); void playlistRemoved(final String ref); void searchFinished(final SearchResult searchResult); } public MultiProviderDatabaseHelper(Context ctx, LocalCallback localCallback) { super(ctx, DATABASE_NAME, null, DATABASE_VERSION); mCallback = localCallback; mSongRefID = new HashMap<>(); mPlaylists = new HashMap<>(); mPlayListRefID = new HashMap<>(); mRefProviderId = new HashMap<>(); mFetched = false; try { mDatabase = getWritableDatabase(); } catch (Exception e) { Log.e(TAG, "Cannot get writable database", e); } } public void setIdentifier(ProviderIdentifier providerIdentifier) { mProviderIdentifier = providerIdentifier; if (!mFetched) { fetchPlaylists(); mFetched = true; } } public boolean isSetup() { return mFetched; } public Song getSong(String ref) throws RemoteException { ProviderIdentifier providerId = mRefProviderId.get(ref); if (providerId != null) { ProviderConnection connection = PluginsLookup.getDefault().getProvider(providerId); if (connection != null) { IMusicProvider binder = connection.getBinder(); if (binder != null) { return binder.getSong(ref); } } } return null; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_PLAYLIST); db.execSQL(CREATE_TABLE_SONGS); } private void fetchPlaylists() { final Cursor c = mDatabase.query(TABLE_PLAYLIST, null, null, null, null, null, null); final int colindex_id = c.getColumnIndex(KEY_ID); final int colindex_name = c.getColumnIndex(KEY_PLAYLIST_NAME); if (c.moveToFirst()) { do { final long playlist_id = c.getLong(colindex_id); // Create the Omni entity Playlist pl = new Playlist("omni:playlist:" + playlist_id); pl.setIsLoaded(true); pl.setName(c.getString(colindex_name)); pl.setProvider(mProviderIdentifier); pl.setSourceLogo(MultiProviderPlaylistProvider.LOGO_REF); // Ensure songs are fetched fetchSongs(pl, playlist_id, mDatabase); // Cache it mPlaylists.put(pl.getRef(), pl); mPlayListRefID.put(pl.getRef(), playlist_id); // Notify the app mCallback.playlistUpdated(pl); } while (c.moveToNext()); } ProviderAggregator.getDefault().getCache().putAllProviderPlaylist(new ArrayList<>(mPlaylists.values())); c.close(); } private void fetchSongs(Playlist playlist, long playlist_id, SQLiteDatabase database) { Cursor c = database.query(TABLE_SONGS, null, KEY_PLAYLIST_ID + "=?", new String[]{Long.toString(playlist_id)}, null, null, KEY_POSITION); final int ci_id = c.getColumnIndex(KEY_ID); final int ci_ref = c.getColumnIndex(KEY_SONG_REF); final int ci_pck = c.getColumnIndex(KEY_PACKAGE_NAME); final int ci_service = c.getColumnIndex(KEY_SERVICE); final int ci_provider = c.getColumnIndex(KEY_PROVIDER); if (c.moveToFirst()) { do { final long song_id = c.getLong(ci_id); String song = c.getString(ci_ref); playlist.addSong(song); ProviderIdentifier providerIdentifier = new ProviderIdentifier(c.getString(ci_pck), c.getString(ci_service), c.getString(ci_provider)); mSongRefID.put(playlist.getRef() + ":" + song, song_id); mRefProviderId.put(song, providerIdentifier); } while (c.moveToNext()); } c.close(); } @Override public void onOpen(SQLiteDatabase db) { if (mProviderIdentifier != null && !mFetched) { fetchPlaylists(); mFetched = true; } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // We have no migrating plan for now db.execSQL("DROP TABLE IF EXISTS " + TABLE_PLAYLIST); db.execSQL("DROP TABLE IF EXISTS " + TABLE_SONGS); onCreate(db); } public List<Playlist> getPlaylists() { return new ArrayList<>(mPlaylists.values()); } public String addPlaylist(String playlist_name) { ContentValues cv = new ContentValues(); cv.put(KEY_PLAYLIST_NAME, playlist_name); long playlist_id = mDatabase.insert(TABLE_PLAYLIST, null, cv); // Generate the Omni entity Playlist pl = new Playlist("omni:playlist:" + playlist_id); pl.setName(playlist_name); pl.setIsLoaded(true); pl.setProvider(mProviderIdentifier); pl.setSourceLogo(MultiProviderPlaylistProvider.LOGO_REF); mPlaylists.put(pl.getRef(), pl); mPlayListRefID.put(pl.getRef(), playlist_id); mCallback.playlistUpdated(pl); return pl.getRef(); } public boolean addSongToPlaylist(String songref, String playlistref, ProviderIdentifier providerIdentifier) { final long playlist_id = Long.parseLong(playlistref.substring(playlistref.lastIndexOf(':') + 1)); Playlist p = mPlaylists.get(playlistref); ContentValues cv = new ContentValues(); cv.put(KEY_PLAYLIST_ID, playlist_id); cv.put(KEY_SONG_REF, songref); cv.put(KEY_PACKAGE_NAME, providerIdentifier.mPackage); cv.put(KEY_SERVICE, providerIdentifier.mService); cv.put(KEY_PROVIDER, providerIdentifier.mName); cv.put(KEY_POSITION, mPlaylists.get(playlistref).songsList().size()); p.addSong(songref); final long song_id = mDatabase.insert(TABLE_SONGS, null, cv); mSongRefID.put(playlistref + ":" + songref, song_id); mCallback.playlistUpdated(p); mRefProviderId.put(songref, providerIdentifier); return true; } public boolean deletePlaylist(String playlistref) { if (mPlayListRefID.containsKey(playlistref)) { long playlist_id = mPlayListRefID.get(playlistref); mDatabase.delete(TABLE_PLAYLIST, KEY_ID + " = ?", new String[]{String.valueOf(playlist_id)}); mDatabase.delete(TABLE_SONGS, KEY_PLAYLIST_ID + " = ?", new String[]{String.valueOf(playlist_id)}); mPlaylists.remove(playlistref); mPlayListRefID.remove(playlistref); mCallback.playlistRemoved(playlistref); return true; } return false; } public boolean renamePlaylist(String playlistRef, String title) { if (mPlayListRefID.containsKey(playlistRef)) { long playlist_id = mPlayListRefID.get(playlistRef); ContentValues cv = new ContentValues(1); cv.put(KEY_PLAYLIST_NAME, title); mDatabase.update(TABLE_PLAYLIST, cv, KEY_PLAYLIST_ID + " = ?", new String[]{String.valueOf(playlist_id)}); return true; } return false; } public boolean deleteSongFromPlaylist(int songPosition, String playlistRef) { String songRef = mPlaylists.get(playlistRef).songsList().get(songPosition); if (mSongRefID.containsKey(playlistRef + ":" + songRef)) { long song_id = mSongRefID.get(playlistRef + ":" + songRef); mDatabase.delete(TABLE_SONGS, KEY_ID + " = ?", new String[]{String.valueOf(song_id)}); mPlaylists.get(playlistRef).songsList().remove(songPosition); mCallback.playlistUpdated(mPlaylists.get(playlistRef)); } return false; } public boolean swapPlaylistItem(int oldPosition, int newPosition, String playlistRef) { String oldSongRef = playlistRef + ":" + mPlaylists.get(playlistRef).songsList().get(oldPosition); String newSongRef = playlistRef + ":" + mPlaylists.get(playlistRef).songsList().get(newPosition); if (mSongRefID.containsKey(oldSongRef) && mSongRefID.containsKey(newSongRef)) { String query = "UPDATE " + TABLE_SONGS + " SET " + KEY_POSITION + "=" + newPosition + " WHERE " + KEY_ID + " = " + mSongRefID.get(oldSongRef); mDatabase.rawQuery(query, null); query = "UPDATE " + TABLE_SONGS + " SET " + KEY_POSITION + "=" + oldPosition + " WHERE " + KEY_ID + " = " + mSongRefID.get(newSongRef); mDatabase.rawQuery(query, null); String oldRef = mPlaylists.get(playlistRef).songsList().get(oldPosition); String newRef = mPlaylists.get(playlistRef).songsList().get(newPosition); mPlaylists.get(playlistRef).songsList().set(oldPosition, newRef); mPlaylists.get(playlistRef).songsList().set(newPosition, oldRef); mCallback.playlistUpdated(mPlaylists.get(playlistRef)); return true; } return false; } public void startSearch(final String query) { if (mSearchResult != null && mSearchResult.getQuery() != null && mSearchResult.getQuery().hashCode() != query.hashCode() || mSearchResult == null) { mSearchResult = new SearchResult(query); final String regex = ".*" + query + ".*"; Thread searchThread = new Thread() { public void run() { Log.d(TAG, "Searching for '" + query + "'"); Pattern p; try { p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); } catch (Exception e) { return; } // MultiProvider only handles playlists, so we only search for that List<String> playlistList = new ArrayList<>(); for (Playlist playlist : mPlaylists.values()) { if (p.matcher(playlist.getName()).matches()) { playlistList.add(playlist.getRef()); } } if (mSearchResult.getQuery().hashCode() == query.hashCode()) { mSearchResult.setPlaylistList(playlistList); mCallback.searchFinished(mSearchResult); } mSearchResult = null; } }; searchThread.start(); } } }