diff --git a/astrid/.classpath b/astrid/.classpath index ec4fdbf6a..0377f788f 100644 --- a/astrid/.classpath +++ b/astrid/.classpath @@ -4,7 +4,7 @@ - + diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index bdb8866f7..583884b4f 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -132,13 +132,13 @@ - + - + diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java index d5c296012..7bba4e675 100644 --- a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java +++ b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java @@ -154,7 +154,7 @@ public abstract class AbstractModel implements Parcelable { if(setValues != null && setValues.containsKey(property.name)) value = setValues.get(property.name); - else if(values != null && values.containsKey(property.name) && (values.get(property.name) != null)) + else if(values != null && values.containsKey(property.name)) value = values.get(property.name); else if(getDefaultValues().containsKey(property.name)) @@ -169,8 +169,10 @@ public abstract class AbstractModel implements Parcelable { return (TYPE) Long.valueOf((String)value); else if(value instanceof String && property instanceof IntegerProperty) return (TYPE) Integer.valueOf((String)value); - if(value instanceof String && property instanceof DoubleProperty) + else if(value instanceof String && property instanceof DoubleProperty) return (TYPE) Double.valueOf((String)value); + else if(value instanceof Integer && property instanceof LongProperty) + return (TYPE) Long.valueOf(((Number)value).longValue()); return (TYPE) value; } diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java index 0f6ef3d4c..dff0637fc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupActivity.java @@ -51,9 +51,8 @@ public class BackupActivity extends Activity { } private void exportTasks() { - /*TasksXmlExporter exporter = new TasksXmlExporter(false); - exporter.setContext(getParent()); - exporter.exportTasks();*/ + TasksXmlExporter.exportTasks(this, false); + finish(); } } diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java index 63d5638a7..c5e344d49 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java @@ -17,7 +17,9 @@ public class BackupConstants { // Do NOT edit the constants in this file! You will break compatibility with old backups - /** Astrid tag (contains everything else) */ + // --- format 2 + + /** Tag containing Astrid backup data */ public static final String ASTRID_TAG = "astrid"; /** Attribute indicating application version */ @@ -26,7 +28,11 @@ public class BackupConstants { /** Attribute indicating backup file format */ public static final String ASTRID_ATTR_FORMAT = "format"; + /** Tag containing a task */ public static final String TASK_TAG = "task"; + + // --- format 1 + public static final String TAG_TAG = "tag"; public static final String TAG_ATTR_NAME = "name"; public static final String ALERT_TAG = "alert"; @@ -34,12 +40,19 @@ public class BackupConstants { public static final String SYNC_TAG = "sync"; public static final String SYNC_ATTR_SERVICE = "service"; public static final String SYNC_ATTR_REMOTE_ID = "remote_id"; + + // --- general + public static final String XML_ENCODING = "utf-8"; public static final String ASTRID_DIR = "/astrid"; + public static final String EXPORT_FILE_NAME = "user.%s.xml"; + public static final String BACKUP_FILE_NAME = "auto.%s.xml"; + // --- methods + /** * @return export directory for tasks, or null if no SD card */ diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java index f0830ebf4..9b8881131 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java @@ -3,265 +3,256 @@ package com.todoroo.astrid.backup; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import org.xmlpull.v1.XmlSerializer; import android.content.Context; -import android.database.Cursor; -import android.os.Environment; import android.util.Log; import android.util.Xml; import android.widget.Toast; import com.timsu.astrid.R; -import com.timsu.astrid.data.TaskController; -import com.timsu.astrid.data.TaskIdentifier; -import com.timsu.astrid.data.TaskModelForXml; -import com.timsu.astrid.data.alerts.AlertController; -import com.timsu.astrid.data.sync.SyncDataController; -import com.timsu.astrid.data.sync.SyncMapping; -import com.timsu.astrid.data.tag.TagController; -import com.timsu.astrid.data.tag.TagIdentifier; -import com.timsu.astrid.data.tag.TagModelForView; +import com.todoroo.andlib.data.AbstractModel; +import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.Property.PropertyVisitor; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.ExceptionService; +import com.todoroo.andlib.sql.Order; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.utility.Preferences; public class TasksXmlExporter { - private TaskController taskController; - private TagController tagController; - private AlertController alertController; - private SyncDataController syncDataController; - private Context ctx; - private String output; + // --- public interface + + /** + * Import tasks from the given file + * + * @param input + * @param runAfterImport + */ + public static void exportTasks(Context context, boolean isService) { + new TasksXmlExporter(context, isService); + } + + // --- implementation + + private static final int FORMAT = 2; + + private final Context context; private final boolean isService; private int exportCount; private XmlSerializer xml; - private HashMap tagMap; - - public static final String ASTRID_TAG = "astrid"; - public static final String ASTRID_ATTR_VERSION = "version"; - public static final String TASK_TAG = "task"; - public static final String TAG_TAG = "tag"; - public static final String TAG_ATTR_NAME = "name"; - public static final String ALERT_TAG = "alert"; - public static final String ALERT_ATTR_DATE = "date"; - public static final String SYNC_TAG = "sync"; - public static final String SYNC_ATTR_SERVICE = "service"; - public static final String SYNC_ATTR_REMOTE_ID = "remote_id"; - public static final String XML_ENCODING = "utf-8"; - public static final String ASTRID_DIR = "/astrid"; - private static final String EXPORT_FILE_NAME = "user.%s.xml"; - private static final String BACKUP_FILE_NAME = "auto.%s.xml"; - public static final int FILENAME_DATE_BEGIN_INDEX = 5; - public static final int FILENAME_DATE_END_INDEX = 11; - - public TasksXmlExporter(boolean isService) { + private final TaskService taskService = PluginServices.getTaskService(); + private final MetadataService metadataService = PluginServices.getMetadataService(); + private final ExceptionService exceptionService = PluginServices.getExceptionService(); + + private TasksXmlExporter(Context context, boolean isService) { + this.context = context; this.isService = isService; this.exportCount = 0; - } - - private void initTagMap() { - tagMap = tagController.getAllTagsAsMap(); - } - private void serializeTags(TaskIdentifier task) - throws IOException { - LinkedList tags = tagController.getTaskTags(task); - for (TagIdentifier tag : tags) { - if(!tagMap.containsKey(tag) || tagMap.get(tag) == null) - continue; - xml.startTag(null, TAG_TAG); - xml.attribute(null, TAG_ATTR_NAME, tagMap.get(tag).toString()); - xml.endTag(null, TAG_TAG); + try { + String output = setupFile(BackupConstants.getExportDirectory()); + doTasksExport(output); + } catch (Exception e) { + if(!isService) + displayErrorToast(e); + exceptionService.reportError("backup-exception", e); //$NON-NLS-1$ + // TODO record last backup error } } - private void serializeSyncMappings(TaskIdentifier task) - throws IOException { - HashSet syncMappings = syncDataController.getSyncMappings(task); - for (SyncMapping sync : syncMappings) { - xml.startTag(null, SYNC_TAG); - xml.attribute(null, SYNC_ATTR_SERVICE, - Integer.toString(sync.getSyncServiceId())); - xml.attribute(null, SYNC_ATTR_REMOTE_ID, sync.getRemoteId()); - xml.endTag(null, SYNC_TAG); - } - } - - private void serializeAlerts(TaskIdentifier task) - throws IOException { - List alerts = alertController.getTaskAlerts(task); - for (Date alert : alerts) { - xml.startTag(null, ALERT_TAG); - xml.attribute(null, ALERT_ATTR_DATE, BackupDateUtilities.getIso8601String(alert)); - xml.endTag(null, ALERT_TAG); - } - } - - private void serializeTasks() - throws IOException { - Cursor c = taskController.getBackupTaskListCursor(); - if (! c.moveToFirst()) { - return; // No tasks. - } - do { - TaskModelForXml task = new TaskModelForXml(c); - TaskIdentifier taskId = task.getTaskIdentifier(); - xml.startTag(null, TASK_TAG); - HashMap taskAttributes = task.getTaskAttributes(); - for (String key : taskAttributes.keySet()) { - String value = taskAttributes.get(key); - xml.attribute(null, key, value); - } - serializeTags(taskId); - serializeAlerts(taskId); - serializeSyncMappings(taskId); - xml.endTag(null, TASK_TAG); - this.exportCount++; - } while (c.moveToNext()); - c.close(); - } - private void doTasksExport() throws IOException { - File xmlFile = new File(this.output); + @SuppressWarnings("nls") + private void doTasksExport(String output) throws IOException { + File xmlFile = new File(output); xmlFile.createNewFile(); FileOutputStream fos = new FileOutputStream(xmlFile); xml = Xml.newSerializer(); - xml.setOutput(fos, XML_ENCODING); + xml.setOutput(fos, BackupConstants.XML_ENCODING); xml.startDocument(null, null); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - xml.startTag(null, ASTRID_TAG); - xml.attribute(null, ASTRID_ATTR_VERSION, - Integer.toString(Preferences.getCurrentVersion(ctx))); + xml.startTag(null, BackupConstants.ASTRID_TAG); + xml.attribute(null, BackupConstants.ASTRID_ATTR_VERSION, + Integer.toString(Preferences.getCurrentVersion())); + xml.attribute(null, BackupConstants.ASTRID_ATTR_FORMAT, + Integer.toString(FORMAT)); - openControllers(); - initTagMap(); serializeTasks(); - closeControllers(); - xml.endTag(null, ASTRID_TAG); + xml.endTag(null, BackupConstants.ASTRID_TAG); xml.endDocument(); xml.flush(); fos.close(); if (!isService) { - displayToast(); + displayToast(output); } } - private void displayToast() { - CharSequence text = String.format(ctx.getString(R.string.export_toast), - ctx.getResources().getQuantityString(R.plurals.Ntasks, exportCount, - exportCount), output); - Toast.makeText(ctx, text, Toast.LENGTH_LONG).show(); + private void serializeTasks() throws IOException { + TodorooCursor cursor = taskService.query(Query.select( + Task.PROPERTIES).orderBy(Order.asc(Task.ID))); + try { + Task task = new Task(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + task.readFromCursor(cursor); + + xml.startTag(null, BackupConstants.TASK_TAG); + serializeModel(task, Task.PROPERTIES); + serializeMetadata(task); + xml.endTag(null, BackupConstants.TASK_TAG); + this.exportCount++; + } + } finally { + cursor.close(); + } } - private void displayErrorToast(String error) { - Toast.makeText(ctx, error, Toast.LENGTH_LONG).show(); + private void serializeMetadata(Task task) throws IOException { + TodorooCursor cursor = metadataService.query(Query.select( + Metadata.PROPERTIES).where(MetadataCriteria.byTask(task.getId()))); + try { + Metadata metadata = new Metadata(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + metadata.readFromCursor(cursor); + + xml.startTag(null, BackupConstants.TAG_TAG); + serializeModel(metadata, Metadata.PROPERTIES); + xml.endTag(null, BackupConstants.TAG_TAG); + } + } finally { + cursor.close(); + } } - private void closeControllers() { - tagController.close(); - taskController.close(); - alertController.close(); - syncDataController.close(); + /** + * Turn a model into xml attributes + * @param model + */ + private void serializeModel(AbstractModel model, Property[] properties) { + for(Property property : properties) { + try { + Log.e("read", "reading " + property.name + " from " + model.getDatabaseValues()); + property.accept(xmlWritingVisitor, model); + } catch (Exception e) { + Log.e("caught", "caught while reading " + property.name + " from " + model.getDatabaseValues(), e); + } + } } - private void openControllers() { - taskController.open(); - tagController.open(); - alertController.open(); - syncDataController.open(); - } + private final XmlWritingPropertyVisitor xmlWritingVisitor = new XmlWritingPropertyVisitor(); + + private class XmlWritingPropertyVisitor implements PropertyVisitor { + @Override + public Void visitInteger(Property property, AbstractModel data) { + try { + xml.attribute(null, property.name, data.getValue(property).toString()); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public Void visitLong(Property property, AbstractModel data) { + try { + xml.attribute(null, property.name, data.getValue(property).toString()); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } - public void exportTasks(File directory) { - if (isService && !Preferences.isBackupEnabled(ctx)) { - // Automatic backups are disabled. - return; + @Override + public Void visitDouble(Property property, AbstractModel data) { + try { + xml.attribute(null, property.name, data.getValue(property).toString()); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; } - if (setupFile(directory)) { - Thread thread = new Thread(doBackgroundExport); - thread.start(); + + @Override + public Void visitString(Property property, AbstractModel data) { + try { + String value = data.getValue(property); + if(value == null) + return null; + xml.attribute(null, property.name, value); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; } + + } + + private void displayToast(String output) { + CharSequence text = String.format(context.getString(R.string.export_toast), + context.getResources().getQuantityString(R.plurals.Ntasks, exportCount, + exportCount), output); + Toast.makeText(context, text, Toast.LENGTH_LONG).show(); + } + + private void displayErrorToast(Exception error) { + Toast.makeText(context, error.toString(), Toast.LENGTH_LONG).show(); } - private boolean setupFile(File directory) { + /** + * Creates directories if necessary and returns fully qualified file + * @param directory + * @return output file name + * @throws IOException + */ + private String setupFile(File directory) throws IOException { File astridDir = directory; if (astridDir != null) { // Check for /sdcard/astrid directory. If it doesn't exist, make it. if (astridDir.exists() || astridDir.mkdir()) { String fileName; if (isService) { - fileName = BACKUP_FILE_NAME; + fileName = BackupConstants.BACKUP_FILE_NAME; } else { - fileName = EXPORT_FILE_NAME; + fileName = BackupConstants.EXPORT_FILE_NAME; } fileName = String.format(fileName, BackupDateUtilities.getDateForExport()); - setOutput(astridDir.getAbsolutePath() + "/" + fileName); - return true; + return astridDir.getAbsolutePath() + File.separator + fileName; } else { // Unable to make the /sdcard/astrid directory. - String error = ctx.getString(R.string.error_sdcard, astridDir.getAbsolutePath()); - Log.e("TasksXmlExporter", error); - if (!isService) { - displayErrorToast(error); - } - return false; + throw new IOException(context.getString(R.string.DLG_error_sdcard, + astridDir.getAbsolutePath())); } } else { // Unable to access the sdcard because it's not in the mounted state. - String error = ctx.getString(R.string.error_sdcard_general); - Log.e("TasksXmlExporter", error); - if (!isService) { - displayErrorToast(error); - } - return false; + throw new IOException(context.getString(R.string.DLG_error_sdcard_general)); } } - private void setOutput(String file) { - this.output = file; - } - - private final Runnable doBackgroundExport = new Runnable() { - public void run() { - /*Looper.prepare(); - try { - doTasksExport(); - } catch (IOException e) { - Log.e("TasksXmlExporter", "IOException in doTasksExport " + e.getMessage()); - } - Looper.loop();*/ - } - }; - - public void setTaskController(TaskController taskController) { - this.taskController = taskController; - } - - public void setTagController(TagController tagController) { - this.tagController = tagController; - } - - public void setAlertController(AlertController alertController) { - this.alertController = alertController; - } - - public void setSyncDataController(SyncDataController syncDataController) { - this.syncDataController = syncDataController; - } - - public void setContext(Context ctx) { - this.ctx = ctx; - setTaskController(new TaskController(ctx)); - setTagController(new TagController(ctx)); - setAlertController(new AlertController(ctx)); - setSyncDataController(new SyncDataController(ctx)); - } } diff --git a/astrid/res/layout/filter_list_activity.xml b/astrid/res/layout/filter_list_activity.xml index ed91846a3..cc2623a60 100644 --- a/astrid/res/layout/filter_list_activity.xml +++ b/astrid/res/layout/filter_list_activity.xml @@ -5,13 +5,6 @@ android:layout_height="fill_parent" android:background="@drawable/background_gradient"> - - - - + diff --git a/samples/filters/bin/astrid-samples-filters.apk b/samples/filters/bin/astrid-samples-filters.apk deleted file mode 100644 index 8a2c9b961..000000000 Binary files a/samples/filters/bin/astrid-samples-filters.apk and /dev/null differ diff --git a/samples/filters/bin/classes.dex b/samples/filters/bin/classes.dex deleted file mode 100644 index aaf3363d6..000000000 Binary files a/samples/filters/bin/classes.dex and /dev/null differ diff --git a/samples/filters/bin/resources.ap_ b/samples/filters/bin/resources.ap_ deleted file mode 100644 index 4f170fa16..000000000 Binary files a/samples/filters/bin/resources.ap_ and /dev/null differ