package org.gingercal; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import org.gingercal.Model.Cal; import org.gingercal.Model.Ev; /** Local cache so the agenda works offline and the phone doesn't refetch on every launch. */ public class Db extends SQLiteOpenHelper implements Model.Store { private static Db instance; public static synchronized Db get(Context c) { if (instance == null) instance = new Db(c.getApplicationContext()); return instance; } private Db(Context c) { super(c, "gingercal.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE cals (href TEXT PRIMARY KEY, name TEXT, color INTEGER, enabled INTEGER," + " writable INTEGER, ctag TEXT, synced INTEGER)"); db.execSQL("CREATE TABLE events (_id INTEGER PRIMARY KEY AUTOINCREMENT, cal TEXT, href TEXT, etag TEXT," + " uid TEXT, title TEXT, location TEXT, descr TEXT, dtstart INTEGER, dtend INTEGER," + " allday INTEGER, recurring INTEGER)"); db.execSQL("CREATE INDEX ev_start ON events(dtstart)"); db.execSQL("CREATE INDEX ev_cal ON events(cal)"); } @Override public void onUpgrade(SQLiteDatabase db, int from, int to) { db.execSQL("DROP TABLE IF EXISTS events"); db.execSQL("DROP TABLE IF EXISTS cals"); onCreate(db); } // ------------------------------------------------------------ calendars public synchronized Map calendars() { Map m = new LinkedHashMap(); Cursor c = getReadableDatabase().rawQuery( "SELECT href, name, color, enabled, writable, ctag, synced FROM cals ORDER BY name", null); try { while (c.moveToNext()) { Cal x = new Cal(); x.href = c.getString(0); x.name = c.getString(1); x.color = c.getInt(2); x.enabled = c.getInt(3) != 0; x.writable = c.getInt(4) != 0; x.ctag = c.getString(5); x.synced = c.getLong(6); m.put(x.href, x); } } finally { c.close(); } return m; } public List calendarList() { return new ArrayList(calendars().values()); } public synchronized void saveCalendar(Cal c) { getWritableDatabase().execSQL("INSERT OR REPLACE INTO cals (href, name, color, enabled, writable, ctag, synced)" + " VALUES (?,?,?,?,?,?,?)", new Object[] {c.href, c.name, Integer.valueOf(c.color), Integer.valueOf(c.enabled ? 1 : 0), Integer.valueOf(c.writable ? 1 : 0), c.ctag, Long.valueOf(c.synced)}); } public synchronized void setEnabled(String href, boolean on) { // reset ctag/synced so the next sync refetches it getWritableDatabase().execSQL("UPDATE cals SET enabled=?, ctag='', synced=0 WHERE href=?", new Object[] {Integer.valueOf(on ? 1 : 0), href}); if (!on) getWritableDatabase().execSQL("DELETE FROM events WHERE cal=?", new Object[] {href}); } public synchronized void deleteCalendar(String href) { SQLiteDatabase db = getWritableDatabase(); db.execSQL("DELETE FROM events WHERE cal=?", new Object[] {href}); db.execSQL("DELETE FROM cals WHERE href=?", new Object[] {href}); } public synchronized void forgetSyncState() { getWritableDatabase().execSQL("UPDATE cals SET ctag='', synced=0"); } public synchronized void clearAll() { SQLiteDatabase db = getWritableDatabase(); db.execSQL("DELETE FROM events"); db.execSQL("DELETE FROM cals"); } // --------------------------------------------------------------- events public synchronized void replaceEvents(String cal, List evs) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { db.execSQL("DELETE FROM events WHERE cal=?", new Object[] {cal}); for (Ev e : evs) { db.execSQL("INSERT INTO events (cal, href, etag, uid, title, location, descr, dtstart, dtend, allday," + " recurring) VALUES (?,?,?,?,?,?,?,?,?,?,?)", new Object[] {cal, e.href, e.etag, e.uid, e.title, e.location, e.descr, Long.valueOf(e.start), Long.valueOf(e.end), Integer.valueOf(e.allDay ? 1 : 0), Integer.valueOf(e.recurring ? 1 : 0)}); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } static final String EV_COLS = "e._id, e.cal, e.href, e.etag, e.uid, e.title, e.location, e.descr, e.dtstart," + " e.dtend, e.allday, e.recurring, c.color, c.writable"; static Ev read(Cursor c) { Ev e = new Ev(); e.id = c.getLong(0); e.cal = c.getString(1); e.href = c.getString(2); e.etag = c.getString(3); e.uid = c.getString(4); e.title = c.getString(5); e.location = c.getString(6); e.descr = c.getString(7); e.start = c.getLong(8); e.end = c.getLong(9); e.allDay = c.getInt(10) != 0; e.recurring = c.getInt(11) != 0; e.color = c.getInt(12); e.writable = c.getInt(13) != 0; return e; } List query(String where, String[] args) { List out = new ArrayList(); Cursor c = getReadableDatabase().rawQuery("SELECT " + EV_COLS + " FROM events e JOIN cals c ON c.href = e.cal" + " WHERE c.enabled = 1 AND (" + where + ") ORDER BY e.dtstart, e.allday DESC, e.title", args); try { while (c.moveToNext()) out.add(read(c)); } finally { c.close(); } return out; } /** Everything still running or starting after `from`. */ public synchronized List agenda(long from, long to) { return query("e.dtend > ? AND e.dtstart < ? OR (e.dtstart = e.dtend AND e.dtstart >= ? AND e.dtstart < ?)", new String[] {String.valueOf(from), String.valueOf(to), String.valueOf(from), String.valueOf(to)}); } public synchronized Ev event(long id) { List l = query("e._id = ?", new String[] {String.valueOf(id)}); return l.isEmpty() ? null : l.get(0); } /** Timed events starting in (after, until]. */ public synchronized List startingBetween(long after, long until) { return query("e.allday = 0 AND e.dtstart > ? AND e.dtstart <= ?", new String[] {String.valueOf(after), String.valueOf(until)}); } public synchronized long nextStartAfter(long after) { Cursor c = getReadableDatabase().rawQuery("SELECT MIN(e.dtstart) FROM events e JOIN cals c ON c.href = e.cal" + " WHERE c.enabled = 1 AND e.allday = 0 AND e.dtstart > ?", new String[] {String.valueOf(after)}); try { if (c.moveToNext() && !c.isNull(0)) return c.getLong(0); return -1; } finally { c.close(); } } public synchronized int count() { Cursor c = getReadableDatabase().rawQuery("SELECT COUNT(*) FROM events", null); try { return c.moveToNext() ? c.getInt(0) : 0; } finally { c.close(); } } }