Wrote an importer for Astrid 2 backup xml files

pull/14/head
Tim Su 14 years ago
parent b6ec30285b
commit 04ea673680

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

@ -2,6 +2,9 @@ package com.todoroo.astrid.backup;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.timsu.astrid.R;
@ -13,7 +16,38 @@ public class BackupActivity extends Activity {
setContentView(R.layout.backup_activity);
setTitle(R.string.backup_BAc_title);
((Button)findViewById(R.id.importButton)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
importTasks();
}
});
((Button)findViewById(R.id.exportButton)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
exportTasks();
}
});
}
private void importTasks() {
FilePickerBuilder.OnFilePickedListener listener = new FilePickerBuilder.OnFilePickedListener() {
@Override
public void onFilePicked(String filePath) {
TasksXmlImporter.importTasks(filePath, null);
}
};
new FilePickerBuilder(this,
getString(R.string.import_file_prompt),
BackupConstants.getExportDirectory(),
listener);
}
private void exportTasks() {
/*TasksXmlExporter exporter = new TasksXmlExporter(false);
exporter.setContext(getParent());
exporter.exportTasks();*/
}
}

@ -1,46 +1,21 @@
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 edu.umd.cs.findbugs.annotations.CheckForNull;
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;
/**
* Constants for backup XML attributes and nodes.
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public class BackupConstants {
private TaskController taskController;
private TagController tagController;
private AlertController alertController;
private SyncDataController syncDataController;
private Context ctx;
private String output;
private final boolean isService;
private int exportCount;
private XmlSerializer xml;
private HashMap<TagIdentifier, TagModelForView> tagMap;
// Do NOT edit the constants in this file! You will break compatibility with old backups
public static final String ASTRID_TAG = "astrid";
public static final String ASTRID_ATTR_VERSION = "version";
@ -53,144 +28,15 @@ public class BackupConstants {
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 BackupConstants(boolean isService) {
this.isService = isService;
this.exportCount = 0;
}
private void initTagMap() {
tagMap = tagController.getAllTagsAsMap();
}
private void serializeTags(TaskIdentifier task)
throws IOException {
LinkedList<TagIdentifier> 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);
}
}
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 {
File xmlFile = new File(this.output);
xmlFile.createNewFile();
FileOutputStream fos = new FileOutputStream(xmlFile);
xml = Xml.newSerializer();
xml.setOutput(fos, 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)));
openControllers();
initTagMap();
serializeTasks();
closeControllers();
xml.endTag(null, ASTRID_TAG);
xml.endDocument();
xml.flush();
fos.close();
if (!isService) {
displayToast();
}
}
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 displayErrorToast(String error) {
Toast.makeText(ctx, error, Toast.LENGTH_LONG).show();
}
private void closeControllers() {
tagController.close();
taskController.close();
alertController.close();
syncDataController.close();
}
private void openControllers() {
taskController.open();
tagController.open();
alertController.open();
syncDataController.open();
}
public void exportTasks(File directory) {
if (isService && !Preferences.isBackupEnabled(ctx)) {
// Automatic backups are disabled.
return;
}
if (setupFile(directory)) {
Thread thread = new Thread(doBackgroundExport);
thread.start();
}
}
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";
/**
* @return export directory for tasks, or null if no SD card
*/
@CheckForNull
public static File getExportDirectory() {
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
@ -201,77 +47,5 @@ public class BackupConstants {
return null;
}
private boolean setupFile(File directory) {
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;
} else {
fileName = EXPORT_FILE_NAME;
}
fileName = String.format(fileName, BackupDateUtilities.getDateForExport());
setOutput(astridDir.getAbsolutePath() + "/" + fileName);
return true;
} 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;
}
} 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;
}
}
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));
}
}

@ -1,4 +1,4 @@
package com.todoroo.astrid.ui;
package com.todoroo.astrid.backup;
import java.io.File;
import java.io.FilenameFilter;

@ -191,16 +191,6 @@ public class TasksXmlExporter {
}
}
public static File getExportDirectory() {
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
path = path + ASTRID_DIR;
return new File(path);
}
return null;
}
private boolean setupFile(File directory) {
File astridDir = directory;
if (astridDir != null) {

@ -1,8 +1,10 @@
package com.todoroo.astrid.backup;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.StringTokenizer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -16,52 +18,53 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.google.ical.values.RRule;
import com.timsu.astrid.R;
import com.timsu.astrid.data.AbstractController;
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.TodorooCursor;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.legacy.LegacyImportance;
import com.todoroo.astrid.legacy.LegacyRepeatInfo;
import com.todoroo.astrid.legacy.LegacyRepeatInfo.LegacyRepeatInterval;
import com.todoroo.astrid.legacy.LegacyTaskModel;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.rmilk.data.MilkTask;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagService;
public class TasksXmlImporter {
public static final String TAG = "TasksXmlImporter";
public static final String ASTRID_TAG = TasksXmlExporter.ASTRID_TAG;
public static final String ASTRID_ATTR_VERSION = TasksXmlExporter.ASTRID_ATTR_VERSION;
public static final String TASK_TAG = TasksXmlExporter.TASK_TAG;
public static final String TAG_TAG = TasksXmlExporter.TAG_TAG;
public static final String ALERT_TAG = TasksXmlExporter.ALERT_TAG;
public static final String SYNC_TAG = TasksXmlExporter.SYNC_TAG;
public static final String TASK_ID = AbstractController.KEY_ROWID;
public static final String TASK_NAME = TaskModelForXml.NAME;
public static final String TASK_CREATION_DATE = TaskModelForXml.CREATION_DATE;
public static final String TAG_ATTR_NAME = TasksXmlExporter.TAG_ATTR_NAME;
public static final String ALERT_ATTR_DATE = TasksXmlExporter.ALERT_ATTR_DATE;
public static final String SYNC_ATTR_SERVICE = TasksXmlExporter.SYNC_ATTR_SERVICE;
public static final String SYNC_ATTR_REMOTE_ID = TasksXmlExporter.SYNC_ATTR_REMOTE_ID;
private TaskController taskController;
private TagController tagController;
private AlertController alertController;
private SyncDataController syncDataController;
private XmlPullParser xpp;
private String input;
private Handler importHandler;
private final Context context;
// --- public interface
/**
* Import tasks from the given file
*
* @param input
* @param runAfterImport
*/
public static void importTasks(String input, Runnable runAfterImport) {
new TasksXmlImporter(input, runAfterImport);
}
// --- implementation
private final Handler importHandler;
private int taskCount;
private int importCount;
private int skipCount;
private final String input;
static ProgressDialog progressDialog;
public TasksXmlImporter(Context context) {
this.context = context;
setContext(context);
}
private final Context context = ContextManager.getContext();
private final TaskService taskService = PluginServices.getTaskService();
private final MetadataService metadataService = PluginServices.getMetadataService();
private final ExceptionService exceptionService = PluginServices.getExceptionService();
protected ProgressDialog progressDialog;
private void setProgressMessage(final String message) {
importHandler.post(new Runnable() {
@ -71,12 +74,18 @@ public class TasksXmlImporter {
});
}
public void importTasks(final Runnable runAfterImport) {
/**
* Import tasks.
* @param runAfterImport optional runnable after import
*/
private TasksXmlImporter(String input, final Runnable runAfterImport) {
this.input = input;
importHandler = new Handler();
importHandler.post(new Runnable() {
@Override
public void run() {
TasksXmlImporter.progressDialog = new ProgressDialog(context);
progressDialog = new ProgressDialog(context);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.import_progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
@ -95,153 +104,61 @@ public class TasksXmlImporter {
if (runAfterImport != null) {
importHandler.post(runAfterImport);
}
} catch (FileNotFoundException e) {
Log.e("TasksXmlImporter", e.getMessage());
} catch (IOException e) {
exceptionService.displayAndReportError(context,
context.getString(R.string.backup_TXI_error), e);
} catch (XmlPullParserException e) {
Log.e("TasksXmlImporter", e.getMessage());
exceptionService.displayAndReportError(context,
context.getString(R.string.backup_TXI_error), e);
}
Looper.loop();
}
}).start();
}
private void performImport() throws FileNotFoundException, XmlPullParserException {
@SuppressWarnings("nls")
private void performImport() throws IOException, XmlPullParserException {
taskCount = 0;
importCount = 0;
skipCount = 0;
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
xpp = factory.newPullParser();
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(input));
setProgressMessage(context.getString(R.string.import_progress_opened));
openControllers();
try {
TaskModelForXml currentTask = null;
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (xpp.getEventType() == XmlPullParser.END_TAG) {
// Ignore end tag.
// Ignore end tags
continue;
}
if (tag != null) {
if (tag.equals(ASTRID_TAG)) {
// Process <astrid ... >
// Perform version compatibility check?
}
else if (tag.equals(TASK_TAG)) {
// Parse <task ... >
currentTask = parseTask();
} else if (currentTask != null) {
// These tags all require that we have a task to associate them with.
if (tag.equals(TAG_TAG)) {
// Process <tag ... >
parseTag(currentTask.getTaskIdentifier());
} else if (tag.equals(ALERT_TAG)) {
// Process <alert ... >
parseAlert(currentTask.getTaskIdentifier());
} else if (tag.equals(SYNC_TAG)) {
// Process <sync ... >
parseSync(currentTask.getTaskIdentifier());
// Process <astrid ... >
if (tag.equals(BackupConstants.ASTRID_TAG)) {
String version = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_VERSION);
int intVersion;
try {
intVersion = Integer.parseInt(version);
} catch (Exception e) {
throw new UnsupportedOperationException(
"Did not know how to import tasks with version '" +
version + "'");
}
if(intVersion <= 135)
new Astrid2TaskImporter(xpp);
else
new Astrid3TaskImporter(xpp);
break;
}
}
}
} catch (Exception e) {
Log.e(TAG, "import error " + e.getMessage());
} finally {
closeControllers();
progressDialog.dismiss();
showSummary();
}
}
private boolean parseSync(TaskIdentifier taskId) {
String service = xpp.getAttributeValue(null, SYNC_ATTR_SERVICE);
String remoteId = xpp.getAttributeValue(null, SYNC_ATTR_REMOTE_ID);
if (service != null && remoteId != null) {
int serviceInt = Integer.parseInt(service);
SyncMapping sm = new SyncMapping(taskId, serviceInt, remoteId);
syncDataController.saveSyncMapping(sm);
return true;
}
return false;
}
private boolean parseAlert(TaskIdentifier taskId) {
String alert = xpp.getAttributeValue(null, ALERT_ATTR_DATE);
if (alert != null) {
Date alertDate = BackupDateUtilities.getDateFromIso8601String(alert);
if (alertDate != null) {
if (! alertController.addAlert(taskId, alertDate)) {
return false;
}
} else {
return false;
}
} else {
return false;
}
return true;
}
private boolean parseTag(TaskIdentifier taskId) {
String tagName = xpp.getAttributeValue(null, TAG_ATTR_NAME);
if (tagName != null) {
TagIdentifier tagId;
TagModelForView tagModel;
tagModel = tagController.fetchTagFromName(tagName);
if (tagModel == null) {
// Tag not found, create a new one.
tagId = tagController.createTag(tagName);
} else {
tagId = tagModel.getTagIdentifier();
}
if (! tagController.addTag(taskId, tagId)) {
return false;
}
} else {
return false;
}
return true;
}
private TaskModelForXml parseTask() {
taskCount++;
setProgressMessage(context.getString(R.string.import_progress_read, taskCount));
TaskModelForXml task = null;
String taskName = xpp.getAttributeValue(null, TASK_NAME);
Date creationDate = null;
String createdString = xpp.getAttributeValue(null, TASK_CREATION_DATE);
if (createdString != null) {
creationDate = BackupDateUtilities.getDateFromIso8601String(createdString);
}
// If the task's name and creation date match an existing task, skip it.
if (creationDate != null && taskName != null) {
task = taskController.fetchTaskForXml(taskName, creationDate);
}
if (task != null) {
// Skip this task.
skipCount++;
setProgressMessage(context.getString(R.string.import_progress_skip, taskCount));
// Set currentTask to null so we skip its alerts/syncs/tags, too.
return null;
}
// Else, make a new task model and add away.
task = new TaskModelForXml();
int numAttributes = xpp.getAttributeCount();
for (int i = 0; i < numAttributes; i++) {
String fieldName = xpp.getAttributeName(i);
String fieldValue = xpp.getAttributeValue(i);
task.setField(fieldName, fieldValue);
}
// Save the task to the database.
taskController.saveTask(task, false);
importCount++;
setProgressMessage(context.getString(R.string.import_progress_add, taskCount));
return task;
}
private void showSummary() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.import_summary_title);
@ -257,44 +174,248 @@ public class TasksXmlImporter {
builder.show();
}
public void setTaskController(TaskController taskController) {
this.taskController = taskController;
}
// --- importers
public void setTagController(TagController tagController) {
this.tagController = tagController;
}
private class Astrid3TaskImporter {
@SuppressWarnings("unused")
private final XmlPullParser xpp;
public void setAlertController(AlertController alertController) {
this.alertController = alertController;
public Astrid3TaskImporter(XmlPullParser xpp) {
this.xpp = xpp;
// TODO
}
}
public void setSyncDataController(SyncDataController syncDataController) {
this.syncDataController = syncDataController;
}
private class Astrid2TaskImporter {
private final XmlPullParser xpp;
private Task currentTask = null;
private String upgradeNotes = null;
private boolean syncOnComplete = false;
public void setContext(Context ctx) {
setTaskController(new TaskController(ctx));
setTagController(new TagController(ctx));
setAlertController(new AlertController(ctx));
setSyncDataController(new SyncDataController(ctx));
}
private final ArrayList<String> tags = new ArrayList<String>();
private void closeControllers() {
taskController.close();
tagController.close();
alertController.close();
syncDataController.close();
}
public Astrid2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp;
private void openControllers() {
taskController.open();
tagController.open();
alertController.open();
syncDataController.open();
}
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG) {
saveTags();
continue;
}
public void setInput(String input) {
this.input = input;
if (tag.equals(BackupConstants.TASK_TAG)) {
// Parse <task ... >
currentTask = parseTask();
} else if (currentTask != null) {
// These tags all require that we have a task to associate
// them with.
if (tag.equals(BackupConstants.TAG_TAG)) {
// Process <tag ... >
parseTag();
} else if (tag.equals(BackupConstants.ALERT_TAG)) {
// Process <alert ... >
parseAlert();
} else if (tag.equals(BackupConstants.SYNC_TAG)) {
// Process <sync ... >
parseSync();
}
}
}
}
private boolean parseSync() {
String service = xpp.getAttributeValue(null, BackupConstants.SYNC_ATTR_SERVICE);
String remoteId = xpp.getAttributeValue(null, BackupConstants.SYNC_ATTR_REMOTE_ID);
if (service != null && remoteId != null) {
StringTokenizer strtok = new StringTokenizer(remoteId, "|"); //$NON-NLS-1$
String taskId = strtok.nextToken();
String taskSeriesId = strtok.nextToken();
String listId = strtok.nextToken();
Metadata metadata = new Metadata();
metadata.setValue(Metadata.TASK, currentTask.getId());
metadata.setValue(MilkTask.LIST_ID, Long.parseLong(listId));
metadata.setValue(MilkTask.TASK_SERIES_ID, Long.parseLong(taskSeriesId));
metadata.setValue(MilkTask.TASK_ID, Long.parseLong(taskId));
metadata.setValue(MilkTask.REPEATING, syncOnComplete ? 1 : 0);
metadataService.save(metadata);
return true;
}
return false;
}
private boolean parseAlert() {
// drop it
return false;
}
private boolean parseTag() {
String tagName = xpp.getAttributeValue(null, BackupConstants.TAG_ATTR_NAME);
tags.add(tagName);
return true;
}
private void saveTags() {
if(currentTask != null && tags.size() > 0) {
TagService.getInstance().synchronizeTags(currentTask.getId(), tags);
}
tags.clear();
}
@SuppressWarnings("nls")
private Task parseTask() {
taskCount++;
setProgressMessage(context.getString(R.string.import_progress_read,
taskCount));
String taskName = xpp.getAttributeValue(null, LegacyTaskModel.NAME);
Date creationDate = null;
String createdString = xpp.getAttributeValue(null,
LegacyTaskModel.CREATION_DATE);
if (createdString != null) {
creationDate = BackupDateUtilities.getDateFromIso8601String(createdString);
}
// if we don't have task name or creation date, skip
if (creationDate == null || taskName == null) {
skipCount++;
return null;
}
// if the task's name and creation date match an existing task, skip
TodorooCursor<Task> cursor = taskService.query(Query.select(Task.ID).where(TaskCriteria.createdOn(creationDate.getTime())));
try {
if(cursor.getCount() > 0) {
skipCount++;
return null;
}
} finally {
cursor.close();
}
// else, make a new task model and add away.
Task task = new Task();
int numAttributes = xpp.getAttributeCount();
for (int i = 0; i < numAttributes; i++) {
String fieldName = xpp.getAttributeName(i);
String fieldValue = xpp.getAttributeValue(i);
if(!setTaskField(task, fieldName, fieldValue)) {
Log.i("astrid-xml-import", "Task: " + taskName + ": Unknown field '" +
fieldName + "' with value '" + fieldValue + "' disregarded.");
}
}
if(upgradeNotes != null) {
if(task.containsValue(Task.NOTES) && task.getValue(Task.NOTES).length() > 0)
task.setValue(Task.NOTES, task.getValue(Task.NOTES) + "\n" + upgradeNotes);
else
task.setValue(Task.NOTES, upgradeNotes);
upgradeNotes = null;
}
// Save the task to the database.
taskService.save(task, false);
importCount++;
setProgressMessage(context.getString(R.string.import_progress_add, taskCount));
return task;
}
/** helper method to set field on a task */
@SuppressWarnings("nls")
private final boolean setTaskField(Task task, String field, String value) {
if(field.equals(LegacyTaskModel.NAME)) {
task.setValue(Task.TITLE, value);
}
else if(field.equals(LegacyTaskModel.NOTES)) {
task.setValue(Task.NOTES, value);
}
else if(field.equals(LegacyTaskModel.PROGRESS_PERCENTAGE)) {
// ignore
}
else if(field.equals(LegacyTaskModel.IMPORTANCE)) {
task.setValue(Task.IMPORTANCE, LegacyImportance.valueOf(value).ordinal());
}
else if(field.equals(LegacyTaskModel.ESTIMATED_SECONDS)) {
task.setValue(Task.ESTIMATED_SECONDS, Integer.parseInt(value));
}
else if(field.equals(LegacyTaskModel.ELAPSED_SECONDS)) {
task.setValue(Task.ELAPSED_SECONDS, Integer.parseInt(value));
}
else if(field.equals(LegacyTaskModel.TIMER_START)) {
task.setValue(Task.TIMER_START,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.DEFINITE_DUE_DATE)) {
String preferred = xpp.getAttributeValue(null, LegacyTaskModel.PREFERRED_DUE_DATE);
if(preferred != null) {
Date preferredDate = BackupDateUtilities.getDateFromIso8601String(value);
upgradeNotes = "Goal Deadline: " +
DateUtilities.getFormattedDate(ContextManager.getContext(),
preferredDate);
}
task.setValue(Task.DUE_DATE,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.PREFERRED_DUE_DATE)) {
String definite = xpp.getAttributeValue(null, LegacyTaskModel.DEFINITE_DUE_DATE);
if(definite != null)
; // handled above
else
task.setValue(Task.DUE_DATE,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.HIDDEN_UNTIL)) {
task.setValue(Task.HIDE_UNTIL,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.BLOCKING_ON)) {
// ignore
}
else if(field.equals(LegacyTaskModel.POSTPONE_COUNT)) {
task.setValue(Task.POSTPONE_COUNT, Integer.parseInt(value));
}
else if(field.equals(LegacyTaskModel.NOTIFICATIONS)) {
task.setValue(Task.REMINDER_PERIOD, Integer.parseInt(value) * 1000L);
}
else if(field.equals(LegacyTaskModel.CREATION_DATE)) {
task.setValue(Task.CREATION_DATE,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.COMPLETION_DATE)) {
task.setValue(Task.COMPLETION_DATE,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals(LegacyTaskModel.NOTIFICATION_FLAGS)) {
task.setValue(Task.REMINDER_FLAGS, Integer.parseInt(value));
}
else if(field.equals(LegacyTaskModel.LAST_NOTIFIED)) {
task.setValue(Task.REMINDER_LAST,
BackupDateUtilities.getDateFromIso8601String(value).getTime());
}
else if(field.equals("repeat_interval")) {
// handled below
}
else if(field.equals("repeat_value")) {
int repeatValue = Integer.parseInt(value);
String repeatInterval = xpp.getAttributeValue(null, "repeat_interval");
if(repeatValue > 0 && repeatInterval != null) {
LegacyRepeatInterval interval = LegacyRepeatInterval.valueOf(repeatInterval);
LegacyRepeatInfo repeatInfo = new LegacyRepeatInfo(interval, repeatValue);
RRule rrule = repeatInfo.toRRule();
task.setValue(Task.RECURRENCE, rrule.toIcal());
}
}
else if(field.equals(LegacyTaskModel.FLAGS)) {
if(Integer.parseInt(value) == LegacyTaskModel.FLAG_SYNC_ON_COMPLETE)
syncOnComplete = true;
}
else {
return false;
}
return true;
}
}
}

@ -2,9 +2,17 @@ package com.todoroo.astrid.core;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
/**
* Utility class for getting dependency-injected services from plugins
*
* @author Tim Su <tim@todoroo.com>
*
*/
public final class PluginServices {
@Autowired
@ -13,6 +21,12 @@ public final class PluginServices {
@Autowired
Database database;
@Autowired
ExceptionService exceptionService;
@Autowired
MetadataService metadataService;
private static PluginServices instance;
private PluginServices() {
@ -30,4 +44,13 @@ public final class PluginServices {
return getInstance().taskService;
}
public static ExceptionService getExceptionService() {
return getInstance().exceptionService;
}
public static MetadataService getMetadataService() {
getInstance().database.openForWriting();
return getInstance().metadataService;
}
}

@ -34,6 +34,11 @@
<!-- backup activity export button -->
<string name="backup_BAc_export">Export Tasks</string>
<!-- ============================================== Importer / Exporter == -->
<!-- Message displayed when error occurs -->
<string name="backup_TXI_error">Import Error</string>
<string name="export_toast">Backed Up %s to %s.</string>
<string name="import_summary_title">Restore Summary</string>
<string name="import_summary_message">

@ -98,6 +98,11 @@ public class TaskDao extends GenericDao<Task> {
return Criterion.or(Task.TITLE.isNull(), Task.TITLE.eq(""));
}
/** @return tasks with this exact creation date */
public static Criterion createdOn(long date) {
return Criterion.or(Task.CREATION_DATE.eq(date));
}
}
// --- custom operations

@ -0,0 +1,10 @@
package com.todoroo.astrid.legacy;
/** Legacy alert class */
@SuppressWarnings("nls")
abstract public class LegacyAlertModel {
public static final String TASK = "task";
public static final String DATE = "date";
}

@ -0,0 +1,32 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.todoroo.astrid.legacy;
public enum LegacyImportance {
// MOST IMPORTANT
LEVEL_1,
LEVEL_2,
LEVEL_3,
LEVEL_4;
// LEAST IMPORTANT
}

@ -0,0 +1,63 @@
package com.todoroo.astrid.legacy;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
/** Legacy repeatInfo class */
public class LegacyRepeatInfo {
public static final int REPEAT_VALUE_OFFSET = 3;
/** Legacy repeat interval class */
public enum LegacyRepeatInterval {
DAYS,
WEEKS,
MONTHS,
HOURS
}
private final LegacyRepeatInterval interval;
private final int value;
public LegacyRepeatInfo(LegacyRepeatInterval repeatInterval, int value) {
this.interval = repeatInterval;
this.value = value;
}
public LegacyRepeatInterval getInterval() {
return interval;
}
public int getValue() {
return value;
}
public static LegacyRepeatInfo fromSingleField(int repeat) {
if(repeat == 0)
return null;
int value = repeat >> REPEAT_VALUE_OFFSET;
LegacyRepeatInterval interval = LegacyRepeatInterval.values()
[repeat - (value << REPEAT_VALUE_OFFSET)];
return new LegacyRepeatInfo(interval, value);
}
public RRule toRRule() {
RRule rrule = new RRule();
rrule.setInterval(getValue());
switch(getInterval()) {
case DAYS:
rrule.setFreq(Frequency.DAILY);
break;
case WEEKS:
rrule.setFreq(Frequency.WEEKLY);
break;
case MONTHS:
rrule.setFreq(Frequency.MONTHLY);
break;
case HOURS:
rrule.setFreq(Frequency.HOURLY);
}
return rrule;
}
}

@ -0,0 +1,32 @@
/*
* ASTRID: Android's Simple Task Recording Dashboard
*
* Copyright (c) 2009 Tim Su
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.todoroo.astrid.legacy;
import com.timsu.astrid.data.AbstractModel;
/** Legacy tag model */
@SuppressWarnings("nls")
abstract public class LegacyTagModel extends AbstractModel {
public static final String NAME = "name";
public static final String NOTES = "notes";
public static final String CREATION_DATE = "creationDate";
}

@ -0,0 +1,37 @@
package com.todoroo.astrid.legacy;
/** Legacy task class */
@SuppressWarnings("nls")
abstract public class LegacyTaskModel {
public static final String NAME = "name";
public static final String NOTES = "notes";
public static final String PROGRESS_PERCENTAGE = "progressPercentage";
public static final String IMPORTANCE = "importance";
public static final String ESTIMATED_SECONDS = "estimatedSeconds";
public static final String ELAPSED_SECONDS = "elapsedSeconds";
public static final String TIMER_START = "timerStart";
public static final String DEFINITE_DUE_DATE = "definiteDueDate";
public static final String PREFERRED_DUE_DATE = "preferredDueDate";
public static final String HIDDEN_UNTIL = "hiddenUntil";
public static final String POSTPONE_COUNT = "postponeCount";
public static final String NOTIFICATIONS = "notifications";
public static final String NOTIFICATION_FLAGS = "notificationFlags";
public static final String LAST_NOTIFIED = "lastNotified";
public static final String REPEAT = "repeat";
public static final String CREATION_DATE = "creationDate";
public static final String COMPLETION_DATE = "completionDate";
public static final String CALENDAR_URI = "calendarUri";
public static final String FLAGS = "flags";
public static final String BLOCKING_ON = "blockingOn";
// notification flags
public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0;
public static final int NOTIFY_AT_DEADLINE = 1 << 1;
public static final int NOTIFY_AFTER_DEADLINE = 1 << 2;
public static final int NOTIFY_NONSTOP = 1 << 3;
// other flags
public static final int FLAG_SYNC_ON_COMPLETE = 1 << 0;
}

@ -18,7 +18,6 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.timsu.astrid.R;
import com.timsu.astrid.utilities.LegacyTasksXmlExporter;
@ -36,6 +35,9 @@ import com.todoroo.astrid.alarms.AlarmDatabase;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.legacy.LegacyAlertModel;
import com.todoroo.astrid.legacy.LegacyRepeatInfo;
import com.todoroo.astrid.legacy.LegacyTaskModel;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.rmilk.data.MilkTask;
@ -128,24 +130,24 @@ public class Astrid2To3UpgradeHelper {
HashMap<String, Property<?>> propertyMap =
new HashMap<String, Property<?>>();
propertyMap.put("_id", Task.ID); //$NON-NLS-1$
propertyMap.put(AbstractTaskModel.NAME, Task.TITLE);
propertyMap.put(AbstractTaskModel.NOTES, Task.NOTES);
propertyMap.put(LegacyTaskModel.NAME, Task.TITLE);
propertyMap.put(LegacyTaskModel.NOTES, Task.NOTES);
// (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.HIDDEN_UNTIL, Task.HIDE_UNTIL);
propertyMap.put(AbstractTaskModel.POSTPONE_COUNT, Task.POSTPONE_COUNT);
propertyMap.put(AbstractTaskModel.NOTIFICATIONS, Task.REMINDER_PERIOD);
propertyMap.put(AbstractTaskModel.NOTIFICATION_FLAGS, Task.REMINDER_FLAGS);
propertyMap.put(AbstractTaskModel.LAST_NOTIFIED, Task.REMINDER_LAST);
propertyMap.put(AbstractTaskModel.REPEAT, Task.RECURRENCE);
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);
propertyMap.put(LegacyTaskModel.IMPORTANCE, Task.IMPORTANCE);
propertyMap.put(LegacyTaskModel.ESTIMATED_SECONDS, Task.ESTIMATED_SECONDS);
propertyMap.put(LegacyTaskModel.ELAPSED_SECONDS, Task.ELAPSED_SECONDS);
propertyMap.put(LegacyTaskModel.TIMER_START, Task.TIMER_START);
propertyMap.put(LegacyTaskModel.DEFINITE_DUE_DATE, Task.DUE_DATE);
propertyMap.put(LegacyTaskModel.HIDDEN_UNTIL, Task.HIDE_UNTIL);
propertyMap.put(LegacyTaskModel.POSTPONE_COUNT, Task.POSTPONE_COUNT);
propertyMap.put(LegacyTaskModel.NOTIFICATIONS, Task.REMINDER_PERIOD);
propertyMap.put(LegacyTaskModel.NOTIFICATION_FLAGS, Task.REMINDER_FLAGS);
propertyMap.put(LegacyTaskModel.LAST_NOTIFIED, Task.REMINDER_LAST);
propertyMap.put(LegacyTaskModel.REPEAT, Task.RECURRENCE);
propertyMap.put(LegacyTaskModel.CREATION_DATE, Task.CREATION_DATE);
propertyMap.put(LegacyTaskModel.COMPLETION_DATE, Task.COMPLETION_DATE);
propertyMap.put(LegacyTaskModel.CALENDAR_URI, Task.CALENDAR_URI);
propertyMap.put(LegacyTaskModel.FLAGS, Task.FLAGS);
upgradeTable(context, tasksTable,
propertyMap, new Task(), taskDao);
@ -157,8 +159,8 @@ public class Astrid2To3UpgradeHelper {
alarmsDatabase.openForWriting();
propertyMap.clear();
propertyMap.put("_id", Alarm.ID); //$NON-NLS-1$
propertyMap.put(Alert.TASK, Alarm.TASK);
propertyMap.put(Alert.DATE, Alarm.TIME);
propertyMap.put(LegacyAlertModel.TASK, Alarm.TASK);
propertyMap.put(LegacyAlertModel.DATE, Alarm.TIME);
upgradeTable(context, alertsTable, propertyMap, new Alarm(),
alarmsDatabase.getDao());
alarmsDatabase.close();
@ -235,7 +237,7 @@ public class Astrid2To3UpgradeHelper {
// special handling for due date
if(property == Task.DUE_DATE) {
long preferredDueDate = data.cursor.getLong(data.cursor.getColumnIndex(AbstractTaskModel.PREFERRED_DUE_DATE));
long preferredDueDate = data.cursor.getLong(data.cursor.getColumnIndex(LegacyTaskModel.PREFERRED_DUE_DATE));
if(value == 0)
value = preferredDueDate;
else if(preferredDueDate != 0) {
@ -262,25 +264,11 @@ public class Astrid2To3UpgradeHelper {
String value = data.cursor.getString(data.columnIndex);
if(property == Task.RECURRENCE) {
RepeatInfo repeatInfo = RepeatInfo.fromSingleField(data.cursor.getInt(data.columnIndex));
LegacyRepeatInfo repeatInfo = LegacyRepeatInfo.fromSingleField(data.cursor.getInt(data.columnIndex));
if(repeatInfo == null)
data.model.setValue(property, "");
else {
RRule rrule = new RRule();
rrule.setInterval(repeatInfo.getValue());
switch(repeatInfo.getInterval()) {
case DAYS:
rrule.setFreq(Frequency.DAILY);
break;
case WEEKS:
rrule.setFreq(Frequency.WEEKLY);
break;
case MONTHS:
rrule.setFreq(Frequency.MONTHLY);
break;
case HOURS:
rrule.setFreq(Frequency.HOURLY);
}
RRule rrule = repeatInfo.toRRule();
data.model.setValue(property, rrule.toIcal());
}
} else {
@ -334,6 +322,7 @@ public class Astrid2To3UpgradeHelper {
container.model.getValue(Task.NOTES) + "\n\n" +
container.upgradeNotes);
}
container.upgradeNotes = null;
}
dao.createNew(container.model);
}
@ -440,6 +429,7 @@ public class Astrid2To3UpgradeHelper {
metadata.setValue(MilkTask.LIST_ID, Long.parseLong(listId));
metadata.setValue(MilkTask.TASK_SERIES_ID, Long.parseLong(taskSeriesId));
metadata.setValue(MilkTask.TASK_ID, Long.parseLong(taskId));
metadata.setValue(MilkTask.REPEATING, 0); // not accurate, but not important
metadataDao.createNew(metadata);
metadata.clearValue(Metadata.ID);
}
@ -448,76 +438,4 @@ public class Astrid2To3UpgradeHelper {
}
}
// --- legacy data structures
/** Legacy repeatInfo class */
private static class RepeatInfo {
public static final int REPEAT_VALUE_OFFSET = 3;
private final RepeatInterval interval;
private final int value;
public RepeatInfo(RepeatInterval repeatInterval, int value) {
this.interval = repeatInterval;
this.value = value;
}
public RepeatInterval getInterval() {
return interval;
}
public int getValue() {
return value;
}
public static RepeatInfo fromSingleField(int 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);
}
}
/** Legacy repeat interval class */
private enum RepeatInterval {
DAYS,
WEEKS,
MONTHS,
HOURS
}
/** Legacy task class */
@SuppressWarnings("nls")
private static abstract class AbstractTaskModel {
public static final String NAME = "name";
public static final String NOTES = "notes";
public static final String IMPORTANCE = "importance";
public static final String ESTIMATED_SECONDS = "estimatedSeconds";
public static final String ELAPSED_SECONDS = "elapsedSeconds";
public static final String TIMER_START = "timerStart";
public static final String DEFINITE_DUE_DATE = "definiteDueDate";
public static final String PREFERRED_DUE_DATE = "preferredDueDate";
public static final String HIDDEN_UNTIL = "hiddenUntil";
public static final String POSTPONE_COUNT = "postponeCount";
public static final String NOTIFICATIONS = "notifications";
public static final String NOTIFICATION_FLAGS = "notificationFlags";
public static final String LAST_NOTIFIED = "lastNotified";
public static final String REPEAT = "repeat";
public static final String CREATION_DATE = "creationDate";
public static final String COMPLETION_DATE = "completionDate";
public static final String CALENDAR_URI = "calendarUri";
public static final String FLAGS = "flags";
}
/** Legacy alert class */
@SuppressWarnings("nls")
private static class Alert {
static final String TASK = "task";
static final String DATE = "date";
}
}

Loading…
Cancel
Save