You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java

581 lines
24 KiB
Java

package com.todoroo.astrid.backup;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.StringTokenizer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import com.google.ical.values.RRule;
import com.timsu.astrid.R;
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.ContextManager;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
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.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagService;
public class TasksXmlImporter {
// --- public interface
/**
* Import tasks from the given file
*
* @param input
* @param runAfterImport
*/
public static void importTasks(Context context, String input, Runnable runAfterImport) {
new TasksXmlImporter(context, input, runAfterImport);
}
// --- implementation
private final Handler handler;
private int taskCount;
private int importCount = 0;
private int skipCount = 0;
private int errorCount = 0;
private final String input;
private final Context context;
private final TaskService taskService = PluginServices.getTaskService();
private final MetadataService metadataService = PluginServices.getMetadataService();
private final ExceptionService exceptionService = PluginServices.getExceptionService();
private final ProgressDialog progressDialog;
private final Runnable runAfterImport;
private void setProgressMessage(final String message) {
handler.post(new Runnable() {
public void run() {
progressDialog.setMessage(message);
}
});
}
/**
* Import tasks.
* @param runAfterImport optional runnable after import
*/
private TasksXmlImporter(final Context context, String input, Runnable runAfterImport) {
this.input = input;
this.context = context;
this.runAfterImport = runAfterImport;
handler = new Handler();
progressDialog = new ProgressDialog(context);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.import_progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.show();
if(context instanceof Activity)
progressDialog.setOwnerActivity((Activity)context);
new Thread(new Runnable() {
@Override
public void run() {
try {
performImport();
} catch (IOException e) {
exceptionService.displayAndReportError(context,
context.getString(R.string.backup_TXI_error), e);
} catch (XmlPullParserException e) {
exceptionService.displayAndReportError(context,
context.getString(R.string.backup_TXI_error), e);
}
}
}).start();
}
@SuppressWarnings("nls")
private void performImport() throws IOException, XmlPullParserException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(input));
try {
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (xpp.getEventType() == XmlPullParser.END_TAG) {
// Ignore end tags
continue;
}
if (tag != null) {
// Process <astrid ... >
if (tag.equals(BackupConstants.ASTRID_TAG)) {
String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT);
if(TextUtils.equals(format, FORMAT1))
new Format1TaskImporter(xpp);
else if(TextUtils.equals(format, FORMAT2))
new Format2TaskImporter(xpp);
else
throw new UnsupportedOperationException(
"Did not know how to import tasks with xml format '" +
format + "'");
}
}
}
} finally {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
handler.post(new Runnable() {
@Override
public void run() {
if(progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
showSummary();
}
});
}
}
private void showSummary() {
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.import_summary_title);
Resources r = context.getResources();
String message = context.getString(R.string.import_summary_message,
input,
r.getQuantityString(R.plurals.Ntasks, taskCount, taskCount),
r.getQuantityString(R.plurals.Ntasks, importCount, importCount),
r.getQuantityString(R.plurals.Ntasks, skipCount, skipCount),
r.getQuantityString(R.plurals.Ntasks, errorCount, errorCount));
builder.setMessage(message);
builder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
if (runAfterImport != null) {
handler.post(runAfterImport);
}
}
});
builder.show();
}
// --- importers
// =============================================================== FORMAT2
private static final String FORMAT2 = "2"; //$NON-NLS-1$
private class Format2TaskImporter {
private final XmlPullParser xpp;
private final Task currentTask = new Task();
private final Metadata metadata = new Metadata();
public Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp;
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG)
continue;
try {
if (tag.equals(BackupConstants.TASK_TAG)) {
// Parse <task ... >
parseTask();
} else if (tag.equals(BackupConstants.METADATA_TAG)) {
// Process <metadata ... >
parseMetadata();
}
} catch (Exception e) {
errorCount++;
Log.e("astrid-importer", //$NON-NLS-1$
"Caught exception while reading from " + //$NON-NLS-1$
xpp.getText(), e);
}
}
}
private void parseTask() {
taskCount++;
setProgressMessage(context.getString(R.string.import_progress_read,
taskCount));
currentTask.clear();
String title = xpp.getAttributeValue(null, Task.TITLE.name);
String created = xpp.getAttributeValue(null, Task.CREATION_DATE.name);
String dueDate = xpp.getAttributeValue(null, Task.DUE_DATE.name);
String completionDate = xpp.getAttributeValue(null, Task.COMPLETION_DATE.name);
// if we don't have task name or creation date, skip
if (created == null || title == null || dueDate == null
|| completionDate == null) {
skipCount++;
return;
}
// if the task's name and creation date match an existing task, skip
TodorooCursor<Task> cursor = taskService.query(Query.select(Task.ID).
where(Criterion.and(Task.TITLE.eq(title),
Task.CREATION_DATE.eq(created),
Task.DUE_DATE.eq(dueDate),
Task.COMPLETION_DATE.eq(completionDate))));
try {
if(cursor.getCount() > 0) {
skipCount++;
return;
}
} finally {
cursor.close();
}
// else, make a new task model and add away.
deserializeModel(currentTask, Task.PROPERTIES);
currentTask.setId(Task.NO_ID);
// Save the task to the database.
taskService.save(currentTask);
importCount++;
}
private void parseMetadata() {
if(!currentTask.isSaved())
return;
metadata.clear();
deserializeModel(metadata, Metadata.PROPERTIES);
metadata.setId(Metadata.NO_ID);
metadata.setValue(Metadata.TASK, currentTask.getId());
metadataService.save(metadata);
}
/**
* Turn a model into xml attributes
* @param model
*/
private void deserializeModel(AbstractModel model, Property<?>[] properties) {
for(Property<?> property : properties) {
try {
property.accept(xmlReadingVisitor, model);
} catch (Exception e) {
Log.e("astrid-importer", //$NON-NLS-1$
"Caught exception while writing " + property.name + //$NON-NLS-1$
" from " + xpp.getText(), e); //$NON-NLS-1$
}
}
}
private final XmlReadingPropertyVisitor xmlReadingVisitor = new XmlReadingPropertyVisitor();
private class XmlReadingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> {
@Override
public Void visitInteger(Property<Integer> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Integer.parseInt(value));
return null;
}
@Override
public Void visitLong(Property<Long> property, AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Long.parseLong(value));
return null;
}
@Override
public Void visitDouble(Property<Double> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, Double.parseDouble(value));
return null;
}
@Override
public Void visitString(Property<String> property,
AbstractModel data) {
String value = xpp.getAttributeValue(null, property.name);
if(value != null)
data.setValue(property, value);
return null;
}
}
}
// =============================================================== FORMAT1
private static final String FORMAT1 = null;
private class Format1TaskImporter {
private final XmlPullParser xpp;
private Task currentTask = null;
private String upgradeNotes = null;
private boolean syncOnComplete = false;
private final LinkedHashSet<String> tags = new LinkedHashSet<String>();
public Format1TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp;
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
try {
if(BackupConstants.TASK_TAG.equals(tag) && xpp.getEventType() == XmlPullParser.END_TAG)
saveTags();
else if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG)
continue;
else 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();
}
}
} catch (Exception e) {
errorCount++;
Log.e("astrid-importer", //$NON-NLS-1$
"Caught exception while reading from " + //$NON-NLS-1$
xpp.getText(), e);
}
}
}
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(Metadata.VALUE1, (listId));
metadata.setValue(Metadata.VALUE2, (taskSeriesId));
metadata.setValue(Metadata.VALUE3, (taskId));
metadata.setValue(Metadata.VALUE4, syncOnComplete ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$
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(Criterion.and(Task.TITLE.eq(taskName),
Task.CREATION_DATE.like(creationDate.getTime()/1000L + "%"))));
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);
importCount++;
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.ID)) {
// ignore
}
else 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 = "Project Deadline: " +
DateUtilities.getDateString(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)) {
String completion = xpp.getAttributeValue(null, LegacyTaskModel.PROGRESS_PERCENTAGE);
if("100".equals(completion)) {
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;
}
}
}