package com.orgzly.android.provider.models;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
import android.text.TextUtils;
import com.orgzly.android.NotePosition;
import com.orgzly.android.provider.DatabaseUtils;
import com.orgzly.android.util.MiscUtils;
import com.orgzly.org.OrgHead;
import com.orgzly.org.datetime.OrgDateTime;
import com.orgzly.org.datetime.OrgRange;
import java.util.List;
/**
* Notes.
*/
public class DbNote {
public static final String TABLE = "notes";
public static final String[] CREATE_SQL = new String[] {
"CREATE TABLE IF NOT EXISTS " + TABLE + " (" +
BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
/* Position/structure */
Columns.BOOK_ID + " INTEGER NOT NULL," +
Columns.POSITION + " INTEGER NOT NULL," +
Columns.LFT + " INTEGER," +
Columns.RGT + " INTEGER," +
Columns.LEVEL + " INTEGER NOT NULL," +
Columns.PARENT_ID + " INTEGER," +
Columns.DESCENDANTS_COUNT + " INTEGER," +
Columns.IS_FOLDED + " INTEGER," +
Columns.FOLDED_UNDER_ID + " INTEGER," +
Columns.IS_CUT + " INTEGER NOT NULL DEFAULT 0," +
/* Payload */
Columns.TITLE + " TEXT NOT NULL DEFAULT ''," +
Columns.TAGS + " TEXT," +
Columns.STATE + " TEXT," +
Columns.PRIORITY + " TEXT," +
Columns.CONTENT + " TEXT," +
Columns.CONTENT_LINE_COUNT + " INTEGER," +
/* Times */
Columns.SCHEDULED_RANGE_ID + " INTEGER," +
Columns.DEADLINE_RANGE_ID + " INTEGER," +
Columns.CLOSED_RANGE_ID + " INTEGER," +
Columns.CLOCK_RANGE_ID + " INTEGER)",
/* For search. */
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.TITLE + " ON " + TABLE + "(" + Columns.TITLE + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.TAGS + " ON " + TABLE + "(" + Columns.TAGS + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.CONTENT + " ON " + TABLE + "(" + Columns.CONTENT + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.BOOK_ID + " ON " + TABLE + "(" + Columns.BOOK_ID + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.IS_CUT + " ON " + TABLE + "(" + Columns.IS_CUT + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.LFT + " ON " + TABLE + "(" + Columns.LFT + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.RGT + " ON " + TABLE + "(" + Columns.RGT + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.IS_FOLDED + " ON " + TABLE + "(" + Columns.IS_FOLDED + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.FOLDED_UNDER_ID + " ON " + TABLE + "(" + Columns.FOLDED_UNDER_ID + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.PARENT_ID + " ON " + TABLE + "(" + Columns.PARENT_ID + ")",
"CREATE INDEX IF NOT EXISTS i_" + TABLE + "_" + Columns.DESCENDANTS_COUNT + " ON " + TABLE + "(" + Columns.DESCENDANTS_COUNT + ")"
};
public static final String DROP_SQL = "DROP TABLE IF EXISTS " + TABLE;
public static String[] POSITION_PROJECTION = new String[] {
Column.BOOK_ID,
Column.LEVEL,
Column.LFT,
Column.RGT,
Column.DESCENDANTS_COUNT,
Column.FOLDED_UNDER_ID,
Column.PARENT_ID,
Column.IS_FOLDED
};
public static void toContentValues(SQLiteDatabase db, ContentValues values, OrgHead head) {
values.put(Column.TITLE, head.getTitle());
values.put(Column.PRIORITY, head.getPriority());
values.put(Column.STATE, head.getState());
if (head.hasTags()) {
values.put(Column.TAGS, dbSerializeTags(head.getTags()));
}
if (head.hasScheduled()) {
values.put(Column.SCHEDULED_RANGE_ID, getOrInsertOrgRange(db, head.getScheduled()));
}
if (head.hasClosed()) {
values.put(Column.CLOSED_RANGE_ID, getOrInsertOrgRange(db, head.getClosed()));
}
if (head.hasClock()) {
values.put(Column.CLOCK_RANGE_ID, getOrInsertOrgRange(db, head.getClock()));
}
if (head.hasDeadline()) {
values.put(Column.DEADLINE_RANGE_ID, getOrInsertOrgRange(db, head.getDeadline()));
}
if (head.hasContent()) {
values.put(Column.CONTENT, head.getContent());
values.put(Column.CONTENT_LINE_COUNT, MiscUtils.lineCount(head.getContent()));
}
}
/**
* Convert to string that will be stored to database.
*/
public static String dbSerializeTags(List tags) {
return TextUtils.join(" ", tags);
}
/**
* Parse string stored in database.
*/
public static String[] dbDeSerializeTags(String str) {
return str.split(" ");
}
/**
* Gets {@link OrgDateTime} from database or inserts a new record if it doesn't exist.
* @return {@link OrgDateTime} database ID
*/
private static long getOrInsertOrgRange(SQLiteDatabase db, OrgRange range) {
long id = DatabaseUtils.getId(
db,
DbOrgRange.TABLE,
DbOrgRange.Column.STRING + "=?",
new String[] { range.toString() });
if (id == 0) {
ContentValues values = new ContentValues();
long startTimestampId = getOrInsertOrgTime(db, range.getStartTime());
long endTimestampId = 0;
if (range.getEndTime() != null) {
endTimestampId = getOrInsertOrgTime(db, range.getEndTime());
}
DbOrgRange.toContentValues(values, range, startTimestampId, endTimestampId);
id = db.insertOrThrow(DbOrgRange.TABLE, null, values);
}
return id;
}
private static long getOrInsertOrgTime(SQLiteDatabase db, OrgDateTime orgDateTime) {
long id = DatabaseUtils.getId(
db,
DbOrgTimestamp.TABLE,
DbOrgTimestamp.Column.STRING + "= ?",
new String[] { orgDateTime.toString() });
if (id == 0) {
ContentValues values = new ContentValues();
DbOrgTimestamp.toContentValues(values, orgDateTime);
id = db.insertOrThrow(DbOrgTimestamp.TABLE, null, values);
}
return id;
}
public static void toContentValues(ContentValues values, NotePosition position) {
values.put(Column.BOOK_ID, position.getBookId());
values.put(Column.LEVEL, position.getLevel());
values.put(Column.LFT, position.getLft());
values.put(Column.RGT, position.getRgt());
values.put(Column.DESCENDANTS_COUNT, position.getDescendantsCount());
values.put(Column.FOLDED_UNDER_ID, position.getFoldedUnderId());
values.put(Column.PARENT_ID, position.getParentId());
values.put(Column.IS_FOLDED, position.isFolded() ? 1 : 0);
values.put(Column.POSITION, 0); // TODO: Remove
}
public static NotePosition positionFromCursor(Cursor cursor) {
long bookId = cursor.getLong(cursor.getColumnIndex(Column.BOOK_ID));
int level = cursor.getInt(cursor.getColumnIndex(Column.LEVEL));
long lft = cursor.getLong(cursor.getColumnIndex(Column.LFT));
long rgt = cursor.getLong(cursor.getColumnIndex(Column.RGT));
int descendantsCount = cursor.getInt(cursor.getColumnIndex(Column.DESCENDANTS_COUNT));
long foldedUnderId = cursor.getLong(cursor.getColumnIndex(Column.FOLDED_UNDER_ID));
long parentId = cursor.getLong(cursor.getColumnIndex(Column.PARENT_ID));
int isFolded = cursor.getInt(cursor.getColumnIndex(Column.IS_FOLDED));
NotePosition position = new NotePosition();
position.setLevel(level);
position.setBookId(bookId);
position.setLft(lft);
position.setRgt(rgt);
position.setDescendantsCount(descendantsCount);
position.setFoldedUnderId(foldedUnderId);
position.setParentId(parentId);
position.setIsFolded(isFolded != 0);
return position;
}
public static NotePosition getPosition(SQLiteDatabase db, long noteId) {
Cursor cursor = db.query(
TABLE,
POSITION_PROJECTION,
Column._ID + " = " + noteId,
null, null, null, null);
try {
if (cursor.moveToFirst()) {
return positionFromCursor(cursor);
} else {
throw new IllegalStateException("Failed getting note for id " + noteId);
}
} finally {
cursor.close();
}
}
public interface Columns {
String BOOK_ID = "book_id";
String POSITION = "position";
String LEVEL = "level";
String TITLE = "title";
String TAGS = "tags";
String STATE = "state";
String PRIORITY = "priority";
String SCHEDULED_RANGE_ID = "scheduled_range_id";
String DEADLINE_RANGE_ID = "deadline_range_id";
String CLOSED_RANGE_ID = "closed_range_id";
String CLOCK_RANGE_ID = "clock_range_id";
String LFT = "is_visible";
String RGT = "parent_position";
String IS_FOLDED = "is_collapsed"; /** Toggleable flag. */
String FOLDED_UNDER_ID = "is_under_collapsed"; /** Hidden due to ancestor being folded. */
String PARENT_ID = "parent_id";
String DESCENDANTS_COUNT = "has_children";
String IS_CUT = "is_cut";
String CONTENT = "content";
String CONTENT_LINE_COUNT = "content_line_count";
}
public static class Column implements Columns, BaseColumns {}
}