package com.quran.labs.androidquran.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import com.quran.labs.androidquran.dao.Bookmark;
import com.quran.labs.androidquran.dao.BookmarkData;
import com.quran.labs.androidquran.dao.RecentPage;
import com.quran.labs.androidquran.dao.Tag;
import com.quran.labs.androidquran.data.Constants;
import com.quran.labs.androidquran.database.BookmarksDBHelper.BookmarkTagTable;
import com.quran.labs.androidquran.database.BookmarksDBHelper.BookmarksTable;
import com.quran.labs.androidquran.database.BookmarksDBHelper.LastPagesTable;
import com.quran.labs.androidquran.database.BookmarksDBHelper.TagsTable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import timber.log.Timber;
public class BookmarksDBAdapter {
public static final int SORT_DATE_ADDED = 0;
public static final int SORT_LOCATION = 1;
private static final int SORT_ALPHABETICAL = 2;
private SQLiteDatabase mDb;
public BookmarksDBAdapter(Context context) {
BookmarksDBHelper dbHelper = BookmarksDBHelper.getInstance(context);
mDb = dbHelper.getWritableDatabase();
}
@NonNull
public List<Bookmark> getBookmarkedAyahsOnPage(int page) {
return getBookmarks(SORT_LOCATION, page);
}
@NonNull
public List<Bookmark> getBookmarks(int sortOrder) {
return getBookmarks(sortOrder, null);
}
@NonNull
private List<Bookmark> getBookmarks(int sortOrder, Integer pageFilter) {
String orderBy;
switch (sortOrder) {
case SORT_LOCATION:
orderBy = BookmarksTable.PAGE + " ASC, "
+ BookmarksTable.SURA + " ASC, " + BookmarksTable.AYAH + " ASC";
break;
case SORT_DATE_ADDED:
default:
orderBy = BookmarksTable.TABLE_NAME + "." + BookmarksTable.ADDED_DATE + " DESC";
}
List<Bookmark> bookmarks = new ArrayList<>();
StringBuilder queryBuilder = new StringBuilder(BookmarksDBHelper.QUERY_BOOKMARKS);
if (pageFilter != null) {
queryBuilder.append(" WHERE ")
.append(BookmarksTable.PAGE)
.append(" = ").append(pageFilter).append(" AND ")
.append(BookmarksTable.SURA).append(" IS NOT NULL").append(" AND ")
.append(BookmarksTable.AYAH).append(" IS NOT NULL");
}
queryBuilder.append(" ORDER BY ").append(orderBy);
Cursor cursor = null;
try {
cursor = mDb.rawQuery(queryBuilder.toString(), null);
if (cursor != null) {
long lastId = -1;
Bookmark lastBookmark = null;
List<Long> tagIds = new ArrayList<>();
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
Integer sura = cursor.getInt(1);
Integer ayah = cursor.getInt(2);
int page = cursor.getInt(3);
long time = cursor.getLong(4);
long tagId = cursor.getLong(5);
if (sura == 0 || ayah == 0) {
sura = null;
ayah = null;
}
if (lastId != id) {
if (lastBookmark != null) {
bookmarks.add(lastBookmark.withTags(tagIds));
}
tagIds.clear();
lastBookmark = new Bookmark(id, sura, ayah, page, time);
lastId = id;
}
if (tagId > 0) {
tagIds.add(tagId);
}
}
if (lastBookmark != null) {
bookmarks.add(lastBookmark.withTags(tagIds));
}
}
} finally {
DatabaseUtils.closeCursor(cursor);
}
return bookmarks;
}
@NonNull
public List<RecentPage> getRecentPages() {
List<RecentPage> recents = new ArrayList<>();
Cursor cursor = null;
try {
cursor = mDb.query(LastPagesTable.TABLE_NAME, null, null, null, null, null,
LastPagesTable.ADDED_DATE + " DESC");
if (cursor != null) {
while (cursor.moveToNext()) {
recents.add(new RecentPage(cursor.getInt(1), cursor.getLong(2)));
}
}
} finally {
DatabaseUtils.closeCursor(cursor);
}
return recents;
}
public boolean replaceRecentRangeWithPage(int deleteRangeStart, int deleteRangeEnd, int page) {
int count = mDb.delete(LastPagesTable.TABLE_NAME,
LastPagesTable.PAGE + " >= ? AND " + LastPagesTable.PAGE + " <= ?",
new String[] { String.valueOf(deleteRangeStart), String.valueOf(deleteRangeEnd) });
// avoid doing a delete if this delete caused us to remove any rows
boolean shouldDelete = count == 0;
return addRecentPage(page, shouldDelete);
}
public boolean addRecentPage(int page) {
return addRecentPage(page, true);
}
private boolean addRecentPage(int page, boolean shouldDelete) {
ContentValues contentValues = new ContentValues();
contentValues.put(LastPagesTable.PAGE, page);
if (mDb.replace(LastPagesTable.TABLE_NAME, null, contentValues) != -1 && shouldDelete) {
mDb.execSQL("DELETE FROM " + LastPagesTable.TABLE_NAME + " WHERE " +
LastPagesTable.ID + " NOT IN( SELECT " + LastPagesTable.ID + " FROM " +
LastPagesTable.TABLE_NAME + " ORDER BY " + LastPagesTable.ADDED_DATE + " DESC LIMIT ? )",
new Object[] { Constants.MAX_RECENT_PAGES });
return true;
}
return false;
}
@NonNull
public List<Long> getBookmarkTagIds(long bookmarkId) {
List<Long> bookmarkTags = new ArrayList<>();
Cursor cursor = null;
try {
cursor = mDb.query(BookmarkTagTable.TABLE_NAME,
new String[]{BookmarkTagTable.TAG_ID},
BookmarkTagTable.BOOKMARK_ID + "=" + bookmarkId,
null, null, null, BookmarkTagTable.TAG_ID + " ASC");
if (cursor != null) {
while (cursor.moveToNext()) {
bookmarkTags.add(cursor.getLong(0));
}
}
} finally {
DatabaseUtils.closeCursor(cursor);
}
return bookmarkTags;
}
public long getBookmarkId(Integer sura, Integer ayah, int page) {
Cursor cursor = null;
try {
cursor = mDb.query(BookmarksTable.TABLE_NAME,
null, BookmarksTable.PAGE + "=" + page + " AND " +
BookmarksTable.SURA + (sura == null ? " IS NULL" : "=" + sura) +
" AND " + BookmarksTable.AYAH +
(ayah == null ? " IS NULL" : "=" + ayah), null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
}
} catch (Exception e) {
// swallow the error for now
} finally {
DatabaseUtils.closeCursor(cursor);
}
return -1;
}
public void bulkDelete(List<Long> tagIds, List<Long> bookmarkIds, List<Pair<Long, Long>> untag) {
mDb.beginTransaction();
try {
// cache and re-use for tags and bookmarks
String[] param = new String[1];
// remove tags
for (int i = 0, tagIdsSize = tagIds.size(); i < tagIdsSize; i++) {
long tagId = tagIds.get(i);
param[0] = String.valueOf(tagId);
mDb.delete(TagsTable.TABLE_NAME, TagsTable.ID + " = ?", param);
mDb.delete(BookmarkTagTable.TABLE_NAME, BookmarkTagTable.TAG_ID + " = ?", param);
}
// remove bookmarks
for (int i = 0, bookmarkIdsSize = bookmarkIds.size(); i < bookmarkIdsSize; i++) {
long bookmarkId = bookmarkIds.get(i);
param[0] = String.valueOf(bookmarkId);
mDb.delete(BookmarksTable.TABLE_NAME, BookmarksTable.ID + " = ?", param);
mDb.delete(BookmarkTagTable.TABLE_NAME, BookmarkTagTable.BOOKMARK_ID + " = ?", param);
}
// untag whatever is being untagged
String[] params = new String[2];
for (int i = 0, untagSize = untag.size(); i < untagSize; i++) {
Pair<Long, Long> item = untag.get(i);
params[0] = String.valueOf(item.first);
params[1] = String.valueOf(item.second);
mDb.delete(BookmarkTagTable.TABLE_NAME,
BookmarkTagTable.BOOKMARK_ID + " = ? AND " + BookmarkTagTable.TAG_ID + " = ?", params);
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
}
public long addBookmarkIfNotExists(Integer sura, Integer ayah, int page) {
long bookmarkId = getBookmarkId(sura, ayah, page);
if (bookmarkId < 0) {
bookmarkId = addBookmark(sura, ayah, page);
}
return bookmarkId;
}
public long addBookmark(Integer sura, Integer ayah, int page) {
ContentValues values = new ContentValues();
values.put(BookmarksTable.SURA, sura);
values.put(BookmarksTable.AYAH, ayah);
values.put(BookmarksTable.PAGE, page);
return mDb.insert(BookmarksTable.TABLE_NAME, null, values);
}
public boolean removeBookmark(long bookmarkId) {
mDb.delete(BookmarkTagTable.TABLE_NAME,
BookmarkTagTable.BOOKMARK_ID + "=" + bookmarkId, null);
return mDb.delete(BookmarksTable.TABLE_NAME,
BookmarksTable.ID + "=" + bookmarkId, null) == 1;
}
@NonNull
public List<Tag> getTags() {
return getTags(SORT_ALPHABETICAL);
}
@NonNull
private List<Tag> getTags(int sortOrder) {
String orderBy;
switch (sortOrder) {
case SORT_DATE_ADDED:
orderBy = TagsTable.ADDED_DATE + " DESC";
break;
case SORT_ALPHABETICAL:
default:
orderBy = TagsTable.NAME + " ASC";
break;
}
List<Tag> tags = new ArrayList<>();
Cursor cursor = null;
try {
cursor = mDb.query(TagsTable.TABLE_NAME,
null, null, null, null, null, orderBy);
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String name = cursor.getString(1);
Tag tag = new Tag(id, name);
tags.add(tag);
}
}
} finally {
DatabaseUtils.closeCursor(cursor);
}
return tags;
}
public long addTag(String name) {
ContentValues values = new ContentValues();
values.put(TagsTable.NAME, name);
return mDb.insert(TagsTable.TABLE_NAME, null, values);
}
public boolean updateTag(long id, String newName) {
ContentValues values = new ContentValues();
values.put(TagsTable.ID, id);
values.put(TagsTable.NAME, newName);
return 1 == mDb.update(TagsTable.TABLE_NAME, values,
TagsTable.ID + "=" + id, null);
}
/**
* Tag a list of bookmarks with a list of tags.
* @param bookmarkIds the list of bookmark ids to tag.
* @param tagIds the tags to tag those bookmarks with.
* @param deleteNonTagged whether or not we should delete all tags not in tagIds from those
* bookmarks or not.
* @return a boolean denoting success
*/
public boolean tagBookmarks(long[] bookmarkIds, Set<Long> tagIds, boolean deleteNonTagged) {
mDb.beginTransaction();
try {
// if we're literally replacing the tags such that only tagIds are tagged, then we need to
// remove all tags from the various bookmarks first.
if (deleteNonTagged) {
for (long bookmarkId : bookmarkIds) {
mDb.delete(BookmarkTagTable.TABLE_NAME,
BookmarkTagTable.BOOKMARK_ID + "=" + bookmarkId, null);
}
}
for (Long tagId : tagIds) {
for (long bookmarkId : bookmarkIds) {
ContentValues values = new ContentValues();
values.put(BookmarkTagTable.BOOKMARK_ID, bookmarkId);
values.put(BookmarkTagTable.TAG_ID, tagId);
mDb.replace(BookmarkTagTable.TABLE_NAME, null, values);
}
}
mDb.setTransactionSuccessful();
return true;
} catch (Exception e) {
Timber.d(e, "exception in tagBookmark");
return false;
} finally {
mDb.endTransaction();
}
}
public boolean importBookmarks(BookmarkData data) {
boolean result = true;
mDb.beginTransaction();
try {
mDb.delete(BookmarksTable.TABLE_NAME, null, null);
mDb.delete(BookmarkTagTable.TABLE_NAME, null, null);
mDb.delete(TagsTable.TABLE_NAME, null, null);
ContentValues values = new ContentValues();
List<Tag> tags = data.getTags();
int tagSize = tags.size();
Timber.d("importing %d tags...", tagSize);
for (int i = 0; i < tagSize; i++) {
Tag tag = tags.get(i);
values.clear();
values.put(TagsTable.NAME, tag.name);
values.put(TagsTable.ID, tag.id);
mDb.insert(TagsTable.TABLE_NAME, null, values);
}
List<Bookmark> bookmarks = data.getBookmarks();
int bookmarkSize = bookmarks.size();
Timber.d("importing %d bookmarks...", bookmarkSize);
for (int i = 0; i < bookmarkSize; i++) {
Bookmark bookmark = bookmarks.get(i);
values.clear();
values.put(BookmarksTable.ID, bookmark.id);
values.put(BookmarksTable.SURA, bookmark.sura);
values.put(BookmarksTable.AYAH, bookmark.ayah);
values.put(BookmarksTable.PAGE, bookmark.page);
values.put(BookmarksTable.ADDED_DATE, bookmark.timestamp);
mDb.insert(BookmarksTable.TABLE_NAME, null, values);
List<Long> tagIds = bookmark.tags;
for (int t = 0; t < tagIds.size(); t++) {
values.clear();
values.put(BookmarkTagTable.BOOKMARK_ID, bookmark.id);
values.put(BookmarkTagTable.TAG_ID, tagIds.get(t));
mDb.insert(BookmarkTagTable.TABLE_NAME, null, values);
}
}
Timber.d("import successful!");
mDb.setTransactionSuccessful();
} catch (Exception e) {
Timber.e(e, "Failed to import data");
result = false;
} finally {
mDb.endTransaction();
}
return result;
}
}