package org.ebookdroid.common.settings.books; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; class DBAdapterV9 implements IDBAdapter { public static final int VERSION = 9; private static final String DB_BOOK_CREATE = "CREATE TABLE book_settings (" // Book file path + "book varchar(1024) primary key, " // Book settings + "book_json varchar(4096)" + ");" ; private static final String DB_BOOK_DEL = "DELETE FROM book_settings WHERE book=?"; private static final String DB_BOOK_DROP = "DROP TABLE IF EXISTS book_settings"; private static final String DB_BOOK_GET_ALL = "SELECT book, book_json FROM book_settings ORDER BY book ASC"; private static final String DB_BOOK_GET_ONE = "SELECT book, book_json FROM book_settings WHERE book=?"; private static final String DB_BOOK_STORE = "INSERT OR REPLACE INTO book_settings (book, book_json) VALUES (? , ?)"; private final DBSettingsManager manager; public DBAdapterV9(final DBSettingsManager manager) { this.manager = manager; } @Override public void onCreate(final SQLiteDatabase db) { db.execSQL(DB_BOOK_CREATE); } @Override public void onDestroy(final SQLiteDatabase db) { db.execSQL(DB_BOOK_DROP); } private void endTransaction(final SQLiteDatabase db) { try { db.endTransaction(); } catch (final Exception ex) { } manager.closeDatabase(db); } private void close(final Cursor c) { try { c.close(); } catch (final Exception ex) { } } private LinkedHashMap<String, BookSettings> makeBookSettingsMap(List<BookSettings> books) { final LinkedHashMap<String, BookSettings> map = new LinkedHashMap<>(); for (BookSettings bs : books) { map.put(bs.fileName, bs); } return map; } private List<BookSettings> getBookSettings(final String query, final String[] args) { final List<BookSettings> list = new ArrayList<>(); try { final SQLiteDatabase db = manager.getReadableDatabase(); try { final Cursor c = db.rawQuery(query, args); if (c != null) { try { for (boolean next = c.moveToFirst(); next; next = c.moveToNext()) { final BookSettings bs = createBookSettings(c); list.add(bs); } } finally { close(c); } } } finally { manager.closeDatabase(db); } } catch (final Throwable th) { LCTX.e("Retrieving book settings failed: ", th); } return list; } @Override public Map<String, BookSettings> getAllBooks() { List<BookSettings> list = getBookSettings(DB_BOOK_GET_ALL, null); return makeBookSettingsMap(list); } @Override public Map<String, BookSettings> getRecentBooks(final boolean all) { List<BookSettings> list = new ArrayList<>(); for (BookSettings bs : getBookSettings(DB_BOOK_GET_ALL, null)) { if (bs.lastUpdated > 0) { list.add(bs); } } Collections.sort(list, new Comparator<BookSettings>() { @Override public int compare(BookSettings bs1, BookSettings bs2) { if (bs1.lastUpdated < bs2.lastUpdated) { return 1; } else if (bs1.lastUpdated == bs2.lastUpdated) { return 0; } else { return -1; } } }); if (!all && list.size() > 0) { list = Collections.singletonList(list.get(0)); } return makeBookSettingsMap(list); } @Override public BookSettings getBookSettings(final String fileName) { List<BookSettings> list = getBookSettings(DB_BOOK_GET_ONE, new String[]{ fileName }); if (list.size() > 0) { return list.get(0); } return null; } private void logBookUpdate(final BookSettings bs) { if (LCTX.isDebugEnabled()) { try { LCTX.d("Store: " + bs.toJSON()); } catch (JSONException e) { LCTX.d("Couldn't serialize " + bs.fileName); } } } private void storeBookSettings(final BookSettings bs, final SQLiteDatabase db) { logBookUpdate(bs); JSONObject bookJSON = null; try { bookJSON = bs.toJSON(); } catch (JSONException e) { throw new RuntimeException(e); } final Object[] args = new Object[] { // File name bs.fileName, // Book json bookJSON.toString() // ... }; db.execSQL(DB_BOOK_STORE, args); } private BookSettings createBookSettings(final Cursor c) { String fileName = c.getString(0); String jsonString = c.getString(1); try { JSONObject jsonObj = new JSONObject(jsonString); BookSettings bs = new BookSettings(jsonObj); if (bs.fileName == null || !bs.fileName.equals(fileName)) { LCTX.e("Name mismatch: " + bs.fileName + " and " + fileName); return null; } return bs; } catch (JSONException e) { LCTX.e("Error parsing JSON: " + e); return null; } } @Override public void delete(final BookSettings current) { executeUpdate(new SQLBlock() { @Override public void run(SQLiteDatabase db) { db.execSQL(DB_BOOK_DEL, new Object[] { current.fileName }); } }); } @Override public boolean storeBookSettings(final List<BookSettings> list) { return executeUpdate(new SQLBlock() { @Override public void run(SQLiteDatabase db) { for (final BookSettings bs : list) { if (bs.lastChanged > 0) { bs.lastUpdated = System.currentTimeMillis(); } storeBookSettings(bs, db); } } }); } @Override public final boolean restoreBookSettings(final Collection<BookSettings> c) { return executeUpdate(new SQLBlock() { @Override public void run(SQLiteDatabase db) { for (final BookSettings bs : c) { storeBookSettings(bs, db); } } }); } @Override public boolean clearRecent() { // TODO: would be better if this was all in a transaction Map<String, BookSettings> allBooks = getAllBooks(); for (BookSettings bs : allBooks.values()) { bs.lastUpdated = 0L; } return storeBookSettings(new ArrayList<>(allBooks.values())); } @Override public boolean deleteAll() { return executeUpdate(new SQLBlock() { @Override public void run(SQLiteDatabase db) { db.execSQL(DB_BOOK_DROP, new Object[] {}); onCreate(db); } }); } @Override public boolean deleteAllBookmarks() { // TODO: would be better if this was all in a transaction Map<String, BookSettings> allBooks = getAllBooks(); for (BookSettings bs : allBooks.values()) { bs.bookmarks.clear(); } storeBookSettings(new ArrayList<>(allBooks.values())); return true; } @Override public boolean removeBookFromRecents(final BookSettings bs) { bs.lastUpdated = 0; return storeBookSettings(Collections.singletonList(bs)); } private interface SQLBlock { void run(SQLiteDatabase db); } private boolean executeUpdate(SQLBlock block) { try { final SQLiteDatabase db = manager.getWritableDatabase(); try { db.beginTransaction(); block.run(db); db.setTransactionSuccessful(); return true; } finally { endTransaction(db); } } catch (final Throwable th) { LCTX.e("executeUpdate: ", th); } return false; } }