Got export working, minor bug fixes

pull/14/head
Tim Su 16 years ago
parent f56b67b316
commit 8082b94c2c

@ -4,7 +4,7 @@
<classpathentry kind="src" path="src-legacy"/> <classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="api-src"/> <classpathentry kind="src" path="api-src"/>
<classpathentry kind="src" path="common-src"/> <classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/backup/TasksXmlExporter.java" kind="src" path="plugin-src"/> <classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/> <classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/>

@ -132,13 +132,13 @@
<!-- core --> <!-- core -->
<receiver android:name="com.todoroo.astrid.core.CorePlugin"> <receiver android:name="com.todoroo.astrid.core.CorePlugin">
<intent-filter android:priority="9000" > <intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_ADDONS" /> <action android:name="com.todoroo.astrid.REQUEST_ADDONS" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.core.CoreFilterExposer"> <receiver android:name="com.todoroo.astrid.core.CoreFilterExposer">
<intent-filter> <intent-filter android:priority="9000">
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" /> <action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>

@ -154,7 +154,7 @@ public abstract class AbstractModel implements Parcelable {
if(setValues != null && setValues.containsKey(property.name)) if(setValues != null && setValues.containsKey(property.name))
value = setValues.get(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); value = values.get(property.name);
else if(getDefaultValues().containsKey(property.name)) else if(getDefaultValues().containsKey(property.name))
@ -169,8 +169,10 @@ public abstract class AbstractModel implements Parcelable {
return (TYPE) Long.valueOf((String)value); return (TYPE) Long.valueOf((String)value);
else if(value instanceof String && property instanceof IntegerProperty) else if(value instanceof String && property instanceof IntegerProperty)
return (TYPE) Integer.valueOf((String)value); 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); return (TYPE) Double.valueOf((String)value);
else if(value instanceof Integer && property instanceof LongProperty)
return (TYPE) Long.valueOf(((Number)value).longValue());
return (TYPE) value; return (TYPE) value;
} }

@ -51,9 +51,8 @@ public class BackupActivity extends Activity {
} }
private void exportTasks() { private void exportTasks() {
/*TasksXmlExporter exporter = new TasksXmlExporter(false); TasksXmlExporter.exportTasks(this, false);
exporter.setContext(getParent()); finish();
exporter.exportTasks();*/
} }
} }

@ -17,7 +17,9 @@ public class BackupConstants {
// Do NOT edit the constants in this file! You will break compatibility with old backups // 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"; public static final String ASTRID_TAG = "astrid";
/** Attribute indicating application version */ /** Attribute indicating application version */
@ -26,7 +28,11 @@ public class BackupConstants {
/** Attribute indicating backup file format */ /** Attribute indicating backup file format */
public static final String ASTRID_ATTR_FORMAT = "format"; public static final String ASTRID_ATTR_FORMAT = "format";
/** Tag containing a task */
public static final String TASK_TAG = "task"; public static final String TASK_TAG = "task";
// --- format 1
public static final String TAG_TAG = "tag"; public static final String TAG_TAG = "tag";
public static final String TAG_ATTR_NAME = "name"; public static final String TAG_ATTR_NAME = "name";
public static final String ALERT_TAG = "alert"; 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_TAG = "sync";
public static final String SYNC_ATTR_SERVICE = "service"; public static final String SYNC_ATTR_SERVICE = "service";
public static final String SYNC_ATTR_REMOTE_ID = "remote_id"; public static final String SYNC_ATTR_REMOTE_ID = "remote_id";
// --- general
public static final String XML_ENCODING = "utf-8"; public static final String XML_ENCODING = "utf-8";
public static final String ASTRID_DIR = "/astrid"; public static final String ASTRID_DIR = "/astrid";
public static final String EXPORT_FILE_NAME = "user.%s.xml"; public static final String EXPORT_FILE_NAME = "user.%s.xml";
public static final String BACKUP_FILE_NAME = "auto.%s.xml"; public static final String BACKUP_FILE_NAME = "auto.%s.xml";
// --- methods
/** /**
* @return export directory for tasks, or null if no SD card * @return export directory for tasks, or null if no SD card
*/ */

@ -3,265 +3,256 @@ package com.todoroo.astrid.backup;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; 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 org.xmlpull.v1.XmlSerializer;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
import android.widget.Toast; import android.widget.Toast;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.data.TaskController; import com.todoroo.andlib.data.AbstractModel;
import com.timsu.astrid.data.TaskIdentifier; import com.todoroo.andlib.data.Property;
import com.timsu.astrid.data.TaskModelForXml; import com.todoroo.andlib.data.Property.PropertyVisitor;
import com.timsu.astrid.data.alerts.AlertController; import com.todoroo.andlib.data.TodorooCursor;
import com.timsu.astrid.data.sync.SyncDataController; import com.todoroo.andlib.service.ExceptionService;
import com.timsu.astrid.data.sync.SyncMapping; import com.todoroo.andlib.sql.Order;
import com.timsu.astrid.data.tag.TagController; import com.todoroo.andlib.sql.Query;
import com.timsu.astrid.data.tag.TagIdentifier; import com.todoroo.astrid.core.PluginServices;
import com.timsu.astrid.data.tag.TagModelForView; 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 { public class TasksXmlExporter {
private TaskController taskController; // --- public interface
private TagController tagController;
private AlertController alertController; /**
private SyncDataController syncDataController; * Import tasks from the given file
private Context ctx; *
private String output; * @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 final boolean isService;
private int exportCount; private int exportCount;
private XmlSerializer xml; private XmlSerializer xml;
private HashMap<TagIdentifier, TagModelForView> tagMap; private final TaskService taskService = PluginServices.getTaskService();
private final MetadataService metadataService = PluginServices.getMetadataService();
public static final String ASTRID_TAG = "astrid"; private final ExceptionService exceptionService = PluginServices.getExceptionService();
public static final String ASTRID_ATTR_VERSION = "version";
public static final String TASK_TAG = "task"; private TasksXmlExporter(Context context, boolean isService) {
public static final String TAG_TAG = "tag"; this.context = context;
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) {
this.isService = isService; this.isService = isService;
this.exportCount = 0; this.exportCount = 0;
}
private void initTagMap() {
tagMap = tagController.getAllTagsAsMap();
}
private void serializeTags(TaskIdentifier task) try {
throws IOException { String output = setupFile(BackupConstants.getExportDirectory());
LinkedList<TagIdentifier> tags = tagController.getTaskTags(task); doTasksExport(output);
for (TagIdentifier tag : tags) { } catch (Exception e) {
if(!tagMap.containsKey(tag) || tagMap.get(tag) == null) if(!isService)
continue; displayErrorToast(e);
xml.startTag(null, TAG_TAG); exceptionService.reportError("backup-exception", e); //$NON-NLS-1$
xml.attribute(null, TAG_ATTR_NAME, tagMap.get(tag).toString()); // TODO record last backup error
xml.endTag(null, TAG_TAG);
} }
} }
private void serializeSyncMappings(TaskIdentifier task)
throws IOException {
HashSet<SyncMapping> 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<Date> 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<String, String> 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 { @SuppressWarnings("nls")
File xmlFile = new File(this.output); private void doTasksExport(String output) throws IOException {
File xmlFile = new File(output);
xmlFile.createNewFile(); xmlFile.createNewFile();
FileOutputStream fos = new FileOutputStream(xmlFile); FileOutputStream fos = new FileOutputStream(xmlFile);
xml = Xml.newSerializer(); xml = Xml.newSerializer();
xml.setOutput(fos, XML_ENCODING); xml.setOutput(fos, BackupConstants.XML_ENCODING);
xml.startDocument(null, null); xml.startDocument(null, null);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, ASTRID_TAG); xml.startTag(null, BackupConstants.ASTRID_TAG);
xml.attribute(null, ASTRID_ATTR_VERSION, xml.attribute(null, BackupConstants.ASTRID_ATTR_VERSION,
Integer.toString(Preferences.getCurrentVersion(ctx))); Integer.toString(Preferences.getCurrentVersion()));
xml.attribute(null, BackupConstants.ASTRID_ATTR_FORMAT,
Integer.toString(FORMAT));
openControllers();
initTagMap();
serializeTasks(); serializeTasks();
closeControllers();
xml.endTag(null, ASTRID_TAG); xml.endTag(null, BackupConstants.ASTRID_TAG);
xml.endDocument(); xml.endDocument();
xml.flush(); xml.flush();
fos.close(); fos.close();
if (!isService) { if (!isService) {
displayToast(); displayToast(output);
} }
} }
private void displayToast() { private void serializeTasks() throws IOException {
CharSequence text = String.format(ctx.getString(R.string.export_toast), TodorooCursor<Task> cursor = taskService.query(Query.select(
ctx.getResources().getQuantityString(R.plurals.Ntasks, exportCount, Task.PROPERTIES).orderBy(Order.asc(Task.ID)));
exportCount), output); try {
Toast.makeText(ctx, text, Toast.LENGTH_LONG).show(); 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) { private void serializeMetadata(Task task) throws IOException {
Toast.makeText(ctx, error, Toast.LENGTH_LONG).show(); TodorooCursor<Metadata> 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(); * Turn a model into xml attributes
taskController.close(); * @param model
alertController.close(); */
syncDataController.close(); 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() { private final XmlWritingPropertyVisitor xmlWritingVisitor = new XmlWritingPropertyVisitor();
taskController.open();
tagController.open(); private class XmlWritingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> {
alertController.open(); @Override
syncDataController.open(); public Void visitInteger(Property<Integer> 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<Long> 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) { @Override
if (isService && !Preferences.isBackupEnabled(ctx)) { public Void visitDouble(Property<Double> property, AbstractModel data) {
// Automatic backups are disabled. try {
return; 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); @Override
thread.start(); public Void visitString(Property<String> 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; File astridDir = directory;
if (astridDir != null) { if (astridDir != null) {
// Check for /sdcard/astrid directory. If it doesn't exist, make it. // Check for /sdcard/astrid directory. If it doesn't exist, make it.
if (astridDir.exists() || astridDir.mkdir()) { if (astridDir.exists() || astridDir.mkdir()) {
String fileName; String fileName;
if (isService) { if (isService) {
fileName = BACKUP_FILE_NAME; fileName = BackupConstants.BACKUP_FILE_NAME;
} else { } else {
fileName = EXPORT_FILE_NAME; fileName = BackupConstants.EXPORT_FILE_NAME;
} }
fileName = String.format(fileName, BackupDateUtilities.getDateForExport()); fileName = String.format(fileName, BackupDateUtilities.getDateForExport());
setOutput(astridDir.getAbsolutePath() + "/" + fileName); return astridDir.getAbsolutePath() + File.separator + fileName;
return true;
} else { } else {
// Unable to make the /sdcard/astrid directory. // Unable to make the /sdcard/astrid directory.
String error = ctx.getString(R.string.error_sdcard, astridDir.getAbsolutePath()); throw new IOException(context.getString(R.string.DLG_error_sdcard,
Log.e("TasksXmlExporter", error); astridDir.getAbsolutePath()));
if (!isService) {
displayErrorToast(error);
}
return false;
} }
} else { } else {
// Unable to access the sdcard because it's not in the mounted state. // Unable to access the sdcard because it's not in the mounted state.
String error = ctx.getString(R.string.error_sdcard_general); throw new IOException(context.getString(R.string.DLG_error_sdcard_general));
Log.e("TasksXmlExporter", error);
if (!isService) {
displayErrorToast(error);
}
return false;
} }
} }
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));
}
} }

@ -5,13 +5,6 @@
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:background="@drawable/background_gradient"> android:background="@drawable/background_gradient">
<!-- Loading Filters label -->
<TextView android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/FLA_loading"
style="@style/TextAppearance.TLA_NoItems"/>
<!-- List --> <!-- List -->
<ExpandableListView android:id="@android:id/list" <ExpandableListView android:id="@android:id/list"
android:layout_width="fill_parent" android:layout_width="fill_parent"

@ -15,19 +15,19 @@ import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ExpandableListView; import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import com.flurry.android.FlurryAgent; import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R; import com.timsu.astrid.R;

@ -3,5 +3,5 @@
<classpathentry kind="src" path="src"/> <classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="ecbuild"/>
</classpath> </classpath>

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save