package yuku.alkitab.base.storage; import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.DatabaseUtils.InsertHelper; import android.database.sqlite.SQLiteDatabase; import android.os.Parcel; import android.support.annotation.Nullable; import android.util.Pair; import yuku.alkitab.base.util.SongBookUtil; import yuku.alkitab.base.util.SongFilter; import yuku.alkitab.base.util.SongFilter.CompiledFilter; import yuku.alkitab.base.util.Sqlitil; import yuku.alkitab.model.SongInfo; import yuku.kpri.model.Song; import java.util.ArrayList; import java.util.List; import static yuku.alkitab.base.util.Literals.Array; import static yuku.alkitab.base.util.Literals.ToStringArray; public class SongDb { public static final String TAG = SongDb.class.getSimpleName(); private SongDbHelper helper; public SongDb(SongDbHelper helper) { this.helper = helper; } private static byte[] marshallSong(Song song, int dataFormatVersion) { Parcel p = Parcel.obtain(); song.writeToParcelCompat(dataFormatVersion, p, 0); byte[] buf = p.marshall(); p.recycle(); return buf; } private static Song unmarshallSong(byte[] buf, int dataFormatVersion) { Parcel p = Parcel.obtain(); p.unmarshall(buf, 0, buf.length); p.setDataPosition(0); Song res = Song.createFromParcelCompat(dataFormatVersion, p); p.recycle(); return res; } /** * Store to db songs in a book. Before the songs are stored, all songs of the specified book are deleted. */ public void storeSongs(String bookName, List<Song> songs, int dataFormatVersion) { SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransactionNonExclusive(); try { // remove existing songs from the same book if any db.delete(Table.SongInfo.tableName(), Table.SongInfo.bookName + "=? and " + Table.SongInfo.dataFormatVersion + "=?", ToStringArray(bookName, dataFormatVersion) ); int ordering = 1; // ordering of the songs for display // insert new ones @SuppressWarnings("deprecation") final InsertHelper ih = new InsertHelper(db, Table.SongInfo.tableName()); int col_bookName = ih.getColumnIndex(Table.SongInfo.bookName.name()); int col_code = ih.getColumnIndex(Table.SongInfo.code.name()); int col_title = ih.getColumnIndex(Table.SongInfo.title.name()); int col_title_original = ih.getColumnIndex(Table.SongInfo.title_original.name()); int col_ordering = ih.getColumnIndex(Table.SongInfo.ordering.name()); int col_dataFormatVersion = ih.getColumnIndex(Table.SongInfo.dataFormatVersion.name()); int col_data = ih.getColumnIndex(Table.SongInfo.data.name()); int col_updateTime = ih.getColumnIndex(Table.SongInfo.updateTime.name()); for (Song song: songs) { ih.prepareForInsert(); ih.bind(col_bookName, bookName); ih.bind(col_code, song.code); ih.bind(col_title, song.title); ih.bind(col_title_original, song.title_original); ih.bind(col_ordering, ordering++); ih.bind(col_dataFormatVersion, dataFormatVersion); ih.bind(col_data, marshallSong(song, dataFormatVersion)); ih.bind(col_updateTime, Sqlitil.nowDateTime()); ih.execute(); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } public Song getSong(String bookName, String code) { SQLiteDatabase db = helper.getReadableDatabase(); String[] columns = new String[] { Table.SongInfo.data.name(), // 0 Table.SongInfo.dataFormatVersion.name(), // 1 }; Cursor c = db.query(Table.SongInfo.tableName(), columns, Table.SongInfo.bookName + "=? and " + Table.SongInfo.code + "=?", new String[]{bookName, code}, null, null, null); try { if (c.moveToNext()) { byte[] data = c.getBlob(0); int dataFormatVersion = c.getInt(1); return unmarshallSong(data, dataFormatVersion); } else { return null; } } finally { c.close(); } } public boolean songExists(String bookName, String code) { SQLiteDatabase db = helper.getReadableDatabase(); Cursor c = db.rawQuery("select count(*) from " + Table.SongInfo.tableName() + " where " + Table.SongInfo.bookName + "=? and " + Table.SongInfo.code + "=?", new String[]{bookName, code}); try { if (c.moveToNext()) { return c.getInt(0) > 0; } else { return false; } } finally { c.close(); } } public Song getFirstSongFromBook(String bookName) { SQLiteDatabase db = helper.getReadableDatabase(); String[] columns = new String[] { // column indexes! Table.SongInfo.data.name(), // 0 Table.SongInfo.dataFormatVersion.name(), // 1 }; Cursor c = db.query(Table.SongInfo.tableName(), columns, Table.SongInfo.bookName + "=?", new String[]{bookName}, null, null, Table.SongInfo.ordering + " asc", "1"); try { if (c.moveToNext()) { byte[] data = c.getBlob(0); int dataFormatVersion = c.getInt(1); return unmarshallSong(data, dataFormatVersion); } else { return null; } } finally { c.close(); } } /** * @return null if there is no song at all */ @Nullable public Pair<String /* bookName */, Song> getAnySong() { final SQLiteDatabase db = helper.getReadableDatabase(); try (Cursor c = db.query(Table.SongInfo.tableName(), ToStringArray(Table.SongInfo.bookName, Table.SongInfo.data, Table.SongInfo.dataFormatVersion), null, null, null, null, Table.SongInfo.bookName + " asc, " + Table.SongInfo.ordering + " asc", "1")) { if (c.moveToNext()) { final String bookName = c.getString(0); final byte[] data = c.getBlob(1); final int dataFormatVersion = c.getInt(2); final Song song = unmarshallSong(data, dataFormatVersion); return Pair.create(bookName, song); } else { return null; } } } public List<SongInfo> listSongInfosByBookName(String bookName) { SQLiteDatabase db = helper.getReadableDatabase(); List<SongInfo> res = new ArrayList<>(); String[] columns = { // column indexes! Table.SongInfo.bookName.name(), // 0 Table.SongInfo.code.name(), // 1 Table.SongInfo.title.name(), // 2 Table.SongInfo.title_original.name(), // 3 }; Cursor c = querySongs(db, columns, bookName); try { while (c.moveToNext()) { String bookName2 = c.getString(0); String code = c.getString(1); String title = c.getString(2); String title_original = c.getString(3); res.add(new SongInfo(bookName2, code, title, title_original)); } } finally { c.close(); } return res; } public List<SongInfo> listSongInfosByBookNameAndDeepFilter(String bookName, String filter_string) { SQLiteDatabase db = helper.getReadableDatabase(); List<SongInfo> res = new ArrayList<>(); String[] columns = { // column indexes! Table.SongInfo.bookName.name(), // 0 Table.SongInfo.code.name(), // 1 Table.SongInfo.title.name(), // 2 Table.SongInfo.title_original.name(), // 3 Table.SongInfo.data.name(), // 4 Table.SongInfo.dataFormatVersion.name(), // 5 }; CompiledFilter cf = SongFilter.compileFilter(filter_string); Cursor c = querySongs(db, columns, bookName); try { while (c.moveToNext()) { String bookName2 = c.getString(0); String code = c.getString(1); String title = c.getString(2); String title_original = c.getString(3); byte[] data = c.getBlob(4); int dataFormatVersion = c.getInt(5); Song song = unmarshallSong(data, dataFormatVersion); if (SongFilter.match(song, cf)) { res.add(new SongInfo(bookName2, code, title, title_original)); } } } finally { c.close(); } return res; } private static Cursor querySongs(SQLiteDatabase db, String[] columns, String bookName) { Cursor c; if (bookName == null) { c = db.query(Table.SongInfo.tableName(), columns, null, null, null, null, Table.SongInfo.bookName + " asc, " + Table.SongInfo.ordering + " asc"); } else { c = db.query(Table.SongInfo.tableName(), columns, Table.SongInfo.bookName + "=?", new String[] {bookName}, null, null, Table.SongInfo.ordering + " asc"); } return c; } /** * Delete song book together with its songs. * @return number of songs deleted */ public int deleteSongBook(final String songBookName) { final SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransactionNonExclusive(); try { // delete song book db.delete(Table.SongBookInfo.tableName(), Table.SongBookInfo.name + "=?", Array(songBookName)); // delete songs final int count = db.delete(Table.SongInfo.tableName(), Table.SongInfo.bookName + "=?", Array(songBookName)); db.setTransactionSuccessful(); return count; } finally { db.endTransaction(); db.execSQL("vacuum"); } } @Nullable public SongBookUtil.SongBookInfo getSongBookInfo(final String name) { final SQLiteDatabase db = helper.getReadableDatabase(); return getSongBookInfo(db, name); } /** * For migration */ public static SongBookUtil.SongBookInfo getSongBookInfo(final SQLiteDatabase db, final String name) { final Cursor c = db.query(Table.SongBookInfo.tableName(), null, Table.SongBookInfo.name + "=?", Array(name), null, null, null); try { if (c.moveToNext()) { final SongBookUtil.SongBookInfo res = new SongBookUtil.SongBookInfo(); res.name = name; res.title = c.getString(c.getColumnIndexOrThrow(Table.SongBookInfo.title.name())); res.copyright = c.getString(c.getColumnIndexOrThrow(Table.SongBookInfo.copyright.name())); return res; } return null; } finally { c.close(); } } public List<SongBookUtil.SongBookInfo> listSongBookInfos() { final SQLiteDatabase db = helper.getReadableDatabase(); final Cursor c = db.query(Table.SongBookInfo.tableName(), null, null, null, null, null, Table.SongBookInfo.name + " asc"); try { final int col_name = c.getColumnIndexOrThrow(Table.SongBookInfo.name.name()); final int col_title = c.getColumnIndexOrThrow(Table.SongBookInfo.title.name()); final int col_copyright = c.getColumnIndexOrThrow(Table.SongBookInfo.copyright.name()); final List<SongBookUtil.SongBookInfo> res = new ArrayList<>(); while (c.moveToNext()) { final SongBookUtil.SongBookInfo info = new SongBookUtil.SongBookInfo(); info.name = c.getString(col_name); info.title = c.getString(col_title); info.copyright = c.getString(col_copyright); res.add(info); } return res; } finally { c.close(); } } /** * Insert a songbook info row. An existing songbook with the same name, if exists, will be deleted. */ public void insertSongBookInfo(final SongBookUtil.SongBookInfo info) { final SQLiteDatabase db = helper.getWritableDatabase(); insertSongBookInfo(db, info); } /** * For migration */ static void insertSongBookInfo(final SQLiteDatabase db, final SongBookUtil.SongBookInfo info) { db.beginTransactionNonExclusive(); try { db.delete(Table.SongBookInfo.tableName(), Table.SongBookInfo.name + "=?", Array(info.name)); final ContentValues cv = new ContentValues(); cv.put(Table.SongBookInfo.name.name(), info.name); cv.put(Table.SongBookInfo.title.name(), info.title); cv.put(Table.SongBookInfo.copyright.name(), info.copyright); db.insert(Table.SongBookInfo.tableName(), null, cv); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } public int getDataFormatVersionForSongs(final String bookName) { final SQLiteDatabase db = helper.getReadableDatabase(); return (int) DatabaseUtils.longForQuery(db, "select " + Table.SongInfo.dataFormatVersion + " from " + Table.SongInfo.tableName() + " where " + Table.SongInfo.bookName + "=? limit 1", Array(bookName)); } public int getSongUpdateTime(final String bookName, final String code) { final SQLiteDatabase db = helper.getReadableDatabase(); return (int) DatabaseUtils.longForQuery(db, "select " + Table.SongInfo.updateTime + " from " + Table.SongInfo.tableName() + " where " + Table.SongInfo.bookName + "=? and " + Table.SongInfo.code + "=?", Array(bookName, code)); } }