diff --git a/api-src/com/todoroo/astrid/api/AstridContentProvider.java b/api-src/com/todoroo/astrid/api/AstridContentProvider.java index e975c018a..61b4bf35f 100644 --- a/api-src/com/todoroo/astrid/api/AstridContentProvider.java +++ b/api-src/com/todoroo/astrid/api/AstridContentProvider.java @@ -106,47 +106,34 @@ public class AstridContentProvider { // --- columns /** long: Task id */ - public static final String ID = AstridApiConstants.TASK_TABLE + "._id"; + public static final String ID = "_id"; /** String: name of Task */ - public static final String TITLE = AstridApiConstants.TASK_TABLE - + ".title"; - - /** - * int: Task Urgency setting (see Task.URGENCY_* for - * possible values) - */ - public static final String URGENCY = AstridApiConstants.TASK_TABLE - + ".urgency"; - - /** - * int: Task Importance setting (see Task.IMPORTANCE_* for - * possible values) - */ - public static final String IMPORTANCE = AstridApiConstants.TASK_TABLE - + ".importance"; + public static final String TITLE = "title"; + + /** int: Task Urgency setting (see Task.URGENCY_*) */ + public static final String URGENCY = "urgency"; + + /** int: Task Importance setting (see Task.IMPORTANCE_*) */ + public static final String IMPORTANCE = "importance"; /** int: unixtime Task is due, 0 if not set */ - public static final String DUE_DATE = AstridApiConstants.TASK_TABLE - + ".dueDate"; + public static final String DUE_DATE = "dueDate"; /** int: unixtime Task should be hidden until, 0 if not set */ - public static final String HIDDEN_UNTIL = AstridApiConstants.TASK_TABLE - + ".hiddenUntil"; + public static final String HIDDEN_UNTIL = "hiddenUntil"; /** int: unixtime Task was created */ - public static final String CREATION_DATE = AstridApiConstants.TASK_TABLE - + ".creationDate"; + public static final String CREATION_DATE = "creationDate"; /** int: unixtime Task was completed, 0 if task not completed */ - public static final String COMPLETION_DATE = AstridApiConstants.TASK_TABLE - + ".completionDate"; + public static final String COMPLETION_DATE = "completionDate"; /** int: unixtime Task was deleted, 0 if task not deleted */ - public static final String DELETION_DATE = AstridApiConstants.TASK_TABLE - + ".deletionDate"; + public static final String DELETION_DATE = "deletionDate"; - public static final String MODIFICATION_DATE = null; + /** int: unixtime Task was modified */ + public static final String MODIFICATION_DATE = "modificationDate"; // --- urgency settings diff --git a/src-legacy/com/timsu/astrid/data/AbstractController.java b/src-legacy/com/timsu/astrid/data/AbstractController.java index 2747ecf9c..e132c54bc 100644 --- a/src-legacy/com/timsu/astrid/data/AbstractController.java +++ b/src-legacy/com/timsu/astrid/data/AbstractController.java @@ -35,11 +35,11 @@ abstract public class AbstractController { public static final String KEY_ROWID = "_id"; // database and table names - protected static final String TASK_TABLE_NAME = "tasks"; - protected static final String TAG_TABLE_NAME = "tags"; - protected static final String TAG_TASK_MAP_NAME = "tagTaskMap"; - protected static final String ALERT_TABLE_NAME = "alerts"; - protected static final String SYNC_TABLE_NAME = "sync"; + public static final String TASK_TABLE_NAME = "tasks"; + public static final String TAG_TABLE_NAME = "tags"; + public static final String TAG_TASK_MAP_NAME = "tagTaskMap"; + public static final String ALERT_TABLE_NAME = "alerts"; + public static final String SYNC_TABLE_NAME = "sync"; abstract public void open(); abstract public void close(); diff --git a/src/com/todoroo/astrid/dao/Database.java b/src/com/todoroo/astrid/dao/Database.java index 1f389ec5e..d5e31386c 100644 --- a/src/com/todoroo/astrid/dao/Database.java +++ b/src/com/todoroo/astrid/dao/Database.java @@ -76,7 +76,7 @@ public final class Database extends AbstractDatabase { * Default implementation of Astrid database helper */ @SuppressWarnings("nls") - public static class AstridSQLiteOpenHelper extends SQLiteOpenHelper { + private static class AstridSQLiteOpenHelper extends SQLiteOpenHelper { public AstridSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { @@ -98,6 +98,7 @@ public final class Database extends AbstractDatabase { for(Property property : table.getProperties()) { if(AbstractModel.ID_PROPERTY.equals(property.name)) continue; + Log.e("haha", table.name + "'s " + property.name); sql.append(',').append(property.accept(sqlVisitor, null)); } sql.append(')'); diff --git a/src/com/todoroo/astrid/model/Metadata.java b/src/com/todoroo/astrid/model/Metadata.java index b3384aaba..d668d6ce1 100644 --- a/src/com/todoroo/astrid/model/Metadata.java +++ b/src/com/todoroo/astrid/model/Metadata.java @@ -45,16 +45,7 @@ public class Metadata extends AbstractModel { TABLE, "value"); /** List of all properties for this model */ - public static final Property[] PROPERTIES = new Property[] { - ID, - TASK, - KEY, - VALUE, - }; - - static { - TABLE.setProperties(PROPERTIES); - } + public static final Property[] PROPERTIES = generateProperties(Metadata.class); // --- defaults diff --git a/src/com/todoroo/astrid/model/Task.java b/src/com/todoroo/astrid/model/Task.java index 009d6d20a..7cb95a32a 100644 --- a/src/com/todoroo/astrid/model/Task.java +++ b/src/com/todoroo/astrid/model/Task.java @@ -8,6 +8,8 @@ package com.todoroo.astrid.model; import android.content.ContentValues; +import com.timsu.astrid.data.enums.RepeatInterval; +import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Table; @@ -73,22 +75,47 @@ public class Task extends AbstractModel { public static final IntegerProperty DELETION_DATE = new IntegerProperty( TABLE, AstridTask.DELETION_DATE); - /** List of all properties for this model */ - public static final Property[] PROPERTIES = new Property[] { - ID, - TITLE, - URGENCY, - IMPORTANCE, - DUE_DATE, - HIDDEN_UNTIL, - CREATION_DATE, - COMPLETION_DATE, - DELETION_DATE - }; + // --- for migration purposes from astrid 2 (eventually we will want to + // move these into the metadata table and treat them as plug-ins - static { - TABLE.setProperties(PROPERTIES); - } + public static final StringProperty NOTES = new StringProperty( + TABLE, "notes"); + + public static final IntegerProperty ESTIMATED_SECONDS = new IntegerProperty( + TABLE, "estimatedSeconds"); + + public static final IntegerProperty ELAPSED_SECONDS = new IntegerProperty( + TABLE, "elapsedSeconds"); + + public static final IntegerProperty TIMER_START = new IntegerProperty( + TABLE, "timerStart"); + + public static final IntegerProperty PREFERRED_DUE_DATE = new IntegerProperty( + TABLE, "preferredDueDate"); + + public static final IntegerProperty POSTPONE_COUNT = new IntegerProperty( + TABLE, "postponeCount"); + + public static final IntegerProperty NOTIFICATIONS = new IntegerProperty( + TABLE, "notifications"); + + public static final IntegerProperty NOTIFICATION_FLAGS = new IntegerProperty( + TABLE, "notificationFlags"); + + public static final IntegerProperty LAST_NOTIFIED = new IntegerProperty( + TABLE, "lastNotified"); + + public static final IntegerProperty REPEAT = new IntegerProperty( + TABLE, "repeat"); + + public static final IntegerProperty FLAGS = new IntegerProperty( + TABLE, "flags"); + + public static final StringProperty CALENDAR_URI = new StringProperty( + TABLE, "calendarUri"); + + /** List of all properties for this model */ + public static final Property[] PROPERTIES = generateProperties(Task.class); // --- urgency flags @@ -197,4 +224,23 @@ public class Task extends AbstractModel { return getValue(DUE_DATE) > 0; } + // --- data access methods for migration properties + + /** Number of bits to shift repeat value by */ + public static final int REPEAT_VALUE_OFFSET = 3; + + /** + * @return RepeatInfo corresponding to + */ + public RepeatInfo getRepeatInfo() { + int repeat = getValue(REPEAT); + if(repeat == 0) + return null; + int value = repeat >> REPEAT_VALUE_OFFSET; + RepeatInterval interval = RepeatInterval.values() + [repeat - (value << REPEAT_VALUE_OFFSET)]; + + return new RepeatInfo(interval, value); + } + } \ No newline at end of file diff --git a/src/com/todoroo/astrid/service/UpgradeService.java b/src/com/todoroo/astrid/service/UpgradeService.java new file mode 100644 index 000000000..0274d94ba --- /dev/null +++ b/src/com/todoroo/astrid/service/UpgradeService.java @@ -0,0 +1,208 @@ +package com.todoroo.astrid.service; + +import java.util.HashMap; +import java.util.Map.Entry; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteDatabase.CursorFactory; + +import com.timsu.astrid.data.AbstractController; +import com.timsu.astrid.data.task.AbstractTaskModel; +import com.todoroo.andlib.data.AbstractDao; +import com.todoroo.andlib.data.AbstractModel; +import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.Property.PropertyVisitor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.model.Task; + + +public final class UpgradeService { + + @Autowired + private Database database; + + @Autowired + private TaskDao taskDao; + + /** + * Perform upgrade from one version to the next. Needs to be called + * on the UI thread so it can display a progress bar and then + * show users a change log. + * + * @param from + * @param to + */ + public void performUpgrade(int from, int to) { + if(from == to) + return; + + if(from < 150) { + upgrade2To3(); + } + } + + // --- database upgrade logic + + /** + * Upgrade helper class that reads a database + */ + private class Astrid2UpgradeHelper extends SQLiteOpenHelper { + + private String name; + + public Astrid2UpgradeHelper(Context context, String name, + CursorFactory factory, int version) { + super(context, name, factory, version); + this.name = name; + } + + @Override + @SuppressWarnings("nls") + public void onCreate(SQLiteDatabase db) { + // create empty table with nothing in it + String sql = "CREATE TABLE IF NOT EXISTS " + name + " (" + + AbstractModel.ID_PROPERTY + " INTEGER);"; + db.execSQL(sql); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // do nothing + } + + } + + /** + * Perform the upgrade from Astrid 2 to Astrid 3 + */ + private void upgrade2To3() { + Context context = ContextManager.getContext(); + + // --- upgrade tasks table + HashMap> propertyMap = + new HashMap>(); + propertyMap.put(AbstractController.KEY_ROWID, Task.ID); + propertyMap.put(AbstractTaskModel.NAME, Task.TITLE); + propertyMap.put(AbstractTaskModel.NOTES, Task.TITLE); + // don't update progress percentage, we don't use this anymore + propertyMap.put(AbstractTaskModel.IMPORTANCE, Task.IMPORTANCE); + propertyMap.put(AbstractTaskModel.ESTIMATED_SECONDS, Task.ESTIMATED_SECONDS); + propertyMap.put(AbstractTaskModel.ELAPSED_SECONDS, Task.ELAPSED_SECONDS); + propertyMap.put(AbstractTaskModel.TIMER_START, Task.TIMER_START); + propertyMap.put(AbstractTaskModel.DEFINITE_DUE_DATE, Task.DUE_DATE); + propertyMap.put(AbstractTaskModel.PREFERRED_DUE_DATE, Task.PREFERRED_DUE_DATE); + propertyMap.put(AbstractTaskModel.HIDDEN_UNTIL, Task.HIDDEN_UNTIL); + propertyMap.put(AbstractTaskModel.POSTPONE_COUNT, Task.POSTPONE_COUNT); + propertyMap.put(AbstractTaskModel.NOTIFICATIONS, Task.NOTIFICATIONS); + propertyMap.put(AbstractTaskModel.NOTIFICATION_FLAGS, Task.NOTIFICATION_FLAGS); + propertyMap.put(AbstractTaskModel.LAST_NOTIFIED, Task.LAST_NOTIFIED); + propertyMap.put(AbstractTaskModel.REPEAT, Task.REPEAT); + propertyMap.put(AbstractTaskModel.CREATION_DATE, Task.CREATION_DATE); + propertyMap.put(AbstractTaskModel.COMPLETION_DATE, Task.COMPLETION_DATE); + propertyMap.put(AbstractTaskModel.CALENDAR_URI, Task.CALENDAR_URI); + propertyMap.put(AbstractTaskModel.FLAGS, Task.FLAGS); + upgradeTasksTable(context, AbstractController.TASK_TABLE_NAME, + propertyMap, new Task(), taskDao); + + // --- upgrade tags table + + } + + protected static class UpgradeVisitorContainer { + public int columnIndex; + public Cursor cursor; + public AbstractModel model; + } + + /** + * Visitor that reads from a visitor container and writes to the model + * @author Tim Su + * + */ + protected class ColumnUpgradeVisitor implements PropertyVisitor { + @Override + public Void visitDouble(Property property, UpgradeVisitorContainer data) { + double value = data.cursor.getDouble(data.columnIndex); + data.model.setValue(property, value); + return null; + } + + @Override + public Void visitInteger(Property property, UpgradeVisitorContainer data) { + int value; + + // convert long date -> integer + if(property == Task.COMPLETION_DATE || + property == Task.CREATION_DATE || + property == Task.DELETION_DATE || + property == Task.DUE_DATE || + property == Task.HIDDEN_UNTIL || + property == Task.LAST_NOTIFIED || + property == Task.MODIFICATION_DATE || + property == Task.PREFERRED_DUE_DATE) + value = (int) (data.cursor.getLong(data.columnIndex) / 1000L); + else + value = data.cursor.getInt(data.columnIndex); + + data.model.setValue(property, value); + return null; + } + + @Override + public Void visitLong(Property property, UpgradeVisitorContainer data) { + long value = data.cursor.getLong(data.columnIndex); + data.model.setValue(property, value); + return null; + } + + @Override + public Void visitString(Property property, UpgradeVisitorContainer data) { + String value = data.cursor.getString(data.columnIndex); + data.model.setValue(property, value); + return null; + } + } + + /** + * Helper that reads entries from legacy database and row-by-row + * creates new models and saves them. + * + * @param context + * @param legacyTable + * @param propertyMap + * @param model + * @param dao + */ + @SuppressWarnings("nls") + private void upgradeTasksTable(Context context, String legacyTable, + HashMap> propertyMap, TYPE model, + AbstractDao dao) { + + SQLiteDatabase upgradeDb = new Astrid2UpgradeHelper(context, legacyTable, + null, 0).getReadableDatabase(); + + Cursor cursor = upgradeDb.rawQuery("SELECT * FROM " + legacyTable, null); + UpgradeVisitorContainer container = new UpgradeVisitorContainer(); + container.cursor = cursor; + container.model = model; + ColumnUpgradeVisitor visitor = new ColumnUpgradeVisitor(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + model.clear(); + for(Entry> entry : propertyMap.entrySet()) { + container.columnIndex = cursor.getColumnIndex(entry.getKey()); + entry.getValue().accept(visitor, container); + } + dao.save(database, container.model); + } + + upgradeDb.close(); + context.deleteDatabase(legacyTable); + } + +} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 24af24bfa..78e3d25bd 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -20,6 +20,6 @@ "adb shell am instrument -w com.xxx.xxx.tests/android.test.InstrumentationTestRunner" --> diff --git a/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java b/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java index 8c87a0ae2..f7a29e9bf 100644 --- a/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java +++ b/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java @@ -22,11 +22,11 @@ public class DatabaseTestCase extends TodorooTestCase { @Override protected void setUp() throws Exception { + super.setUp(); + // create new test database - database.clear(); + database.clear(); database.openForWriting(); - - super.setUp(); } @Override diff --git a/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java b/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java index b4de9b2ef..b9930cd40 100644 --- a/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java +++ b/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java @@ -25,6 +25,8 @@ public class Astrid2To3UpgradeTests extends DatabaseTestCase { } public static void assertDatesEqual(Date old, int newDate) { + if(old == null) + assertEquals(0, newDate); assertEquals(old.getTime() / 1000L, newDate); } @@ -44,10 +46,12 @@ public class Astrid2To3UpgradeTests extends DatabaseTestCase { TaskModelForEdit guti = new com.todoroo.astrid.legacy.data.task.TaskModelForEdit(); guti.setName("franklin gutierrez"); guti.setPreferredDueDate(new Date(System.currentTimeMillis() + 5000000L)); + guti.setImportance(Importance.LEVEL_4); guti.setHiddenUntil(new Date()); guti.setRepeat(new RepeatInfo(RepeatInterval.DAYS, 10)); guti.setElapsedSeconds(500); taskController.saveTask(guti, false); + Date createdDate = new Date(); // assert created assertEquals(2, taskController.getAllTaskIdentifiers()); @@ -62,12 +66,30 @@ public class Astrid2To3UpgradeTests extends DatabaseTestCase { // verify that data exists in our new table TodorooCursor tasks = taskDao.query(database, Query.select(Task.PROPERTIES)); + assertEquals(2, tasks.getCount()); + tasks.moveToFirst(); Task task = new Task(tasks, Task.PROPERTIES); assertEquals(griffey.getName(), task.getValue(Task.TITLE)); assertDatesEqual(griffey.getDefiniteDueDate(), task.getValue(Task.DUE_DATE)); assertEquals((Integer)Task.IMPORTANCE_SHOULD_DO, task.getValue(Task.IMPORTANCE)); - + assertEquals(griffey.getEstimatedSeconds(), task.getValue(Task.ESTIMATED_SECONDS)); + assertEquals(griffey.getNotes(), task.getValue(Task.NOTES)); + assertEquals((Integer)0, task.getValue(Task.LAST_NOTIFIED)); + assertEquals((Integer)0, task.getValue(Task.HIDDEN_UNTIL)); + + tasks.moveToNext(); + task = new Task(tasks, Task.PROPERTIES); + assertEquals(guti.getName(), task.getValue(Task.TITLE)); + assertDatesEqual(guti.getDefiniteDueDate(), task.getValue(Task.DUE_DATE)); + assertDatesEqual(guti.getPreferredDueDate(), task.getValue(Task.DUE_DATE)); + assertDatesEqual(guti.getHiddenUntil(), task.getValue(Task.HIDDEN_UNTIL)); + assertEquals((Integer)Task.IMPORTANCE_DO_OR_DIE, task.getValue(Task.IMPORTANCE)); + assertEquals(guti.getRepeat().getValue(), task.getRepeatInfo().getValue()); + assertEquals(guti.getRepeat().getInterval().ordinal(), task.getRepeatInfo().getInterval().ordinal()); + assertEquals(guti.getElapsedSeconds(), task.getValue(Task.ELAPSED_SECONDS)); + assertEquals(createdDate, task.getValue(Task.CREATION_DATE)); + assertEquals(createdDate, task.getValue(Task.MODIFICATION_DATE)); }