mirror of https://github.com/tasks/tasks
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.
386 lines
14 KiB
Java
386 lines
14 KiB
Java
12 years ago
|
/**
|
||
|
* Copyright (c) 2012 Todoroo Inc
|
||
|
*
|
||
|
* See the file "LICENSE" for the full license governing this code.
|
||
|
*/
|
||
14 years ago
|
package com.todoroo.astrid.backup;
|
||
15 years ago
|
|
||
14 years ago
|
import android.app.Activity;
|
||
15 years ago
|
import android.app.ProgressDialog;
|
||
14 years ago
|
import android.content.Intent;
|
||
14 years ago
|
import android.content.res.Resources;
|
||
15 years ago
|
import android.os.Handler;
|
||
14 years ago
|
import android.text.TextUtils;
|
||
14 years ago
|
|
||
8 years ago
|
import com.google.common.collect.Iterables;
|
||
14 years ago
|
import com.todoroo.andlib.data.AbstractModel;
|
||
|
import com.todoroo.andlib.data.Property;
|
||
14 years ago
|
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
||
14 years ago
|
import com.todoroo.andlib.sql.Criterion;
|
||
14 years ago
|
import com.todoroo.andlib.sql.Query;
|
||
13 years ago
|
import com.todoroo.andlib.utility.DialogUtilities;
|
||
14 years ago
|
import com.todoroo.astrid.api.AstridApiConstants;
|
||
10 years ago
|
import com.todoroo.astrid.dao.MetadataDao;
|
||
10 years ago
|
import com.todoroo.astrid.dao.TagDataDao;
|
||
8 years ago
|
import com.todoroo.astrid.dao.TaskDao;
|
||
8 years ago
|
import com.todoroo.astrid.dao.UserActivityDao;
|
||
14 years ago
|
import com.todoroo.astrid.data.Metadata;
|
||
11 years ago
|
import com.todoroo.astrid.data.TagData;
|
||
14 years ago
|
import com.todoroo.astrid.data.Task;
|
||
8 years ago
|
import com.todoroo.astrid.data.UserActivity;
|
||
11 years ago
|
import com.todoroo.astrid.tags.TaskToTagMetadata;
|
||
15 years ago
|
|
||
11 years ago
|
import org.tasks.R;
|
||
9 years ago
|
import org.tasks.dialogs.DialogBuilder;
|
||
11 years ago
|
import org.xmlpull.v1.XmlPullParser;
|
||
|
import org.xmlpull.v1.XmlPullParserException;
|
||
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||
|
|
||
|
import java.io.FileReader;
|
||
|
import java.io.IOException;
|
||
8 years ago
|
import java.util.List;
|
||
11 years ago
|
|
||
10 years ago
|
import javax.inject.Inject;
|
||
10 years ago
|
|
||
9 years ago
|
import timber.log.Timber;
|
||
14 years ago
|
|
||
9 years ago
|
public class TasksXmlImporter {
|
||
11 years ago
|
|
||
10 years ago
|
private final TagDataDao tagDataDao;
|
||
10 years ago
|
private final MetadataDao metadataDao;
|
||
8 years ago
|
private final UserActivityDao userActivityDao;
|
||
9 years ago
|
private final DialogBuilder dialogBuilder;
|
||
8 years ago
|
private final TaskDao taskDao;
|
||
11 years ago
|
|
||
8 years ago
|
private Activity activity;
|
||
10 years ago
|
private Handler handler;
|
||
15 years ago
|
private int taskCount;
|
||
14 years ago
|
private int importCount = 0;
|
||
|
private int skipCount = 0;
|
||
|
private int errorCount = 0;
|
||
10 years ago
|
private ProgressDialog progressDialog;
|
||
|
private String input;
|
||
15 years ago
|
|
||
|
private void setProgressMessage(final String message) {
|
||
8 years ago
|
handler.post(() -> progressDialog.setMessage(message));
|
||
15 years ago
|
}
|
||
|
|
||
10 years ago
|
@Inject
|
||
8 years ago
|
public TasksXmlImporter(TagDataDao tagDataDao, MetadataDao metadataDao, UserActivityDao userActivityDao,
|
||
8 years ago
|
DialogBuilder dialogBuilder, TaskDao taskDao) {
|
||
10 years ago
|
this.tagDataDao = tagDataDao;
|
||
10 years ago
|
this.metadataDao = metadataDao;
|
||
8 years ago
|
this.userActivityDao = userActivityDao;
|
||
9 years ago
|
this.dialogBuilder = dialogBuilder;
|
||
8 years ago
|
this.taskDao = taskDao;
|
||
10 years ago
|
}
|
||
|
|
||
8 years ago
|
public void importTasks(Activity activity, String input, ProgressDialog progressDialog) {
|
||
|
this.activity = activity;
|
||
14 years ago
|
this.input = input;
|
||
8 years ago
|
this.progressDialog = progressDialog;
|
||
14 years ago
|
|
||
|
handler = new Handler();
|
||
14 years ago
|
|
||
8 years ago
|
new Thread(() -> {
|
||
|
try {
|
||
|
performImport();
|
||
|
} catch (IOException | XmlPullParserException e) {
|
||
|
Timber.e(e, e.getMessage());
|
||
15 years ago
|
}
|
||
|
}).start();
|
||
|
}
|
||
|
|
||
14 years ago
|
private void performImport() throws IOException, XmlPullParserException {
|
||
15 years ago
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||
14 years ago
|
XmlPullParser xpp = factory.newPullParser();
|
||
15 years ago
|
xpp.setInput(new FileReader(input));
|
||
|
|
||
|
try {
|
||
|
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
|
||
|
String tag = xpp.getName();
|
||
|
if (xpp.getEventType() == XmlPullParser.END_TAG) {
|
||
14 years ago
|
// Ignore end tags
|
||
15 years ago
|
continue;
|
||
|
}
|
||
|
if (tag != null) {
|
||
14 years ago
|
// Process <astrid ... >
|
||
|
if (tag.equals(BackupConstants.ASTRID_TAG)) {
|
||
14 years ago
|
String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT);
|
||
10 years ago
|
if(TextUtils.equals(format, FORMAT2)) {
|
||
14 years ago
|
new Format2TaskImporter(xpp);
|
||
11 years ago
|
} else if(TextUtils.equals(format, FORMAT3)) {
|
||
|
new Format3TaskImporter(xpp);
|
||
11 years ago
|
} else {
|
||
14 years ago
|
throw new UnsupportedOperationException(
|
||
14 years ago
|
"Did not know how to import tasks with xml format '" +
|
||
11 years ago
|
format + "'");
|
||
|
}
|
||
15 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
} finally {
|
||
14 years ago
|
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
||
8 years ago
|
activity.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||
8 years ago
|
handler.post(() -> {
|
||
|
if(progressDialog.isShowing()) {
|
||
|
DialogUtilities.dismissDialog(activity, progressDialog);
|
||
|
showSummary();
|
||
14 years ago
|
}
|
||
|
});
|
||
15 years ago
|
}
|
||
|
}
|
||
|
|
||
|
private void showSummary() {
|
||
8 years ago
|
Resources r = activity.getResources();
|
||
9 years ago
|
dialogBuilder.newDialog()
|
||
|
.setTitle(R.string.import_summary_title)
|
||
8 years ago
|
.setMessage(activity.getString(R.string.import_summary_message,
|
||
9 years ago
|
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)))
|
||
8 years ago
|
.setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.dismiss())
|
||
9 years ago
|
.show();
|
||
15 years ago
|
}
|
||
|
|
||
14 years ago
|
// --- importers
|
||
15 years ago
|
|
||
14 years ago
|
// =============================================================== FORMAT2
|
||
|
|
||
|
private static final String FORMAT2 = "2"; //$NON-NLS-1$
|
||
|
private class Format2TaskImporter {
|
||
|
|
||
8 years ago
|
XmlPullParser xpp;
|
||
8 years ago
|
final Task currentTask = new Task();
|
||
8 years ago
|
final UserActivity userActivity = new UserActivity();
|
||
8 years ago
|
final Metadata metadata = new Metadata();
|
||
|
final TagData tagdata = new TagData();
|
||
15 years ago
|
|
||
11 years ago
|
public Format2TaskImporter() { }
|
||
14 years ago
|
public Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||
14 years ago
|
this.xpp = xpp;
|
||
13 years ago
|
|
||
14 years ago
|
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
|
||
|
String tag = xpp.getName();
|
||
11 years ago
|
if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG) {
|
||
14 years ago
|
continue;
|
||
11 years ago
|
}
|
||
14 years ago
|
|
||
|
try {
|
||
|
if (tag.equals(BackupConstants.TASK_TAG)) {
|
||
|
// Parse <task ... >
|
||
|
parseTask();
|
||
8 years ago
|
} else if (tag.equals(BackupConstants.COMMENT_TAG)) {
|
||
|
// Process <comment ... >
|
||
|
parseComment();
|
||
14 years ago
|
} else if (tag.equals(BackupConstants.METADATA_TAG)) {
|
||
|
// Process <metadata ... >
|
||
11 years ago
|
parseMetadata(2);
|
||
14 years ago
|
}
|
||
|
} catch (Exception e) {
|
||
|
errorCount++;
|
||
9 years ago
|
Timber.e(e, e.getMessage());
|
||
14 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
8 years ago
|
void parseTask() {
|
||
14 years ago
|
taskCount++;
|
||
8 years ago
|
setProgressMessage(activity.getString(R.string.import_progress_read, taskCount));
|
||
14 years ago
|
currentTask.clear();
|
||
|
|
||
|
String title = xpp.getAttributeValue(null, Task.TITLE.name);
|
||
|
String created = xpp.getAttributeValue(null, Task.CREATION_DATE.name);
|
||
|
|
||
|
// if we don't have task name or creation date, skip
|
||
13 years ago
|
if (created == null || title == null) {
|
||
14 years ago
|
skipCount++;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if the task's name and creation date match an existing task, skip
|
||
8 years ago
|
Query query = Query.select(Task.ID, Task.COMPLETION_DATE, Task.DELETION_DATE)
|
||
|
.where(Criterion.and(Task.TITLE.eq(title), Task.CREATION_DATE.eq(created)));
|
||
|
if (taskDao.count(query) > 0) {
|
||
|
skipCount++;
|
||
11 years ago
|
} else {
|
||
8 years ago
|
deserializeModel(currentTask, Task.PROPERTIES);
|
||
|
|
||
13 years ago
|
currentTask.setId(Task.NO_ID);
|
||
14 years ago
|
|
||
8 years ago
|
// Save the task to the database.
|
||
|
taskDao.save(currentTask);
|
||
|
importCount++;
|
||
|
}
|
||
14 years ago
|
}
|
||
|
|
||
8 years ago
|
/**
|
||
|
* Imports a comment from the XML we're reading.
|
||
|
* taken from EditNoteActivity.addComment()
|
||
|
*/
|
||
|
void parseComment() {
|
||
|
if (!currentTask.isSaved()) {
|
||
|
return;
|
||
|
}
|
||
|
userActivity.clear();
|
||
|
|
||
|
// We only want to import these fields.
|
||
|
// These are the fields that are sent in EditNoteActivity.addComment() when a user creates a comment.
|
||
|
final Property<?>[] NEW_PROPERTIES = new Property<?>[4];
|
||
|
NEW_PROPERTIES[0] = UserActivity.MESSAGE;
|
||
|
NEW_PROPERTIES[1] = UserActivity.ACTION;
|
||
|
NEW_PROPERTIES[2] = UserActivity.TARGET_ID;
|
||
|
NEW_PROPERTIES[3] = UserActivity.CREATED_AT;
|
||
|
deserializeModel(userActivity, NEW_PROPERTIES);
|
||
|
|
||
|
userActivity.setId(UserActivity.NO_ID);
|
||
|
|
||
|
userActivityDao.createNew(userActivity);
|
||
|
}
|
||
|
|
||
8 years ago
|
void parseMetadata(int format) {
|
||
11 years ago
|
if(!currentTask.isSaved()) {
|
||
14 years ago
|
return;
|
||
11 years ago
|
}
|
||
14 years ago
|
metadata.clear();
|
||
|
deserializeModel(metadata, Metadata.PROPERTIES);
|
||
8 years ago
|
if (metadata.getKey().equals(TaskToTagMetadata.KEY)) {
|
||
|
String uuid = metadata.getValue(TaskToTagMetadata.TAG_UUID);
|
||
|
List<Metadata> metadatas = metadataDao.byTaskAndKey(currentTask.getId(), TaskToTagMetadata.KEY);
|
||
|
if (Iterables.any(metadatas, existing -> uuid.equals(existing.getValue(TaskToTagMetadata.TAG_UUID)))) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
14 years ago
|
metadata.setId(Metadata.NO_ID);
|
||
11 years ago
|
metadata.setTask(currentTask.getId());
|
||
10 years ago
|
metadataDao.persist(metadata);
|
||
11 years ago
|
|
||
|
// Construct the TagData from Metadata
|
||
|
// Fix for failed backup, Version before 4.6.10
|
||
|
if (format == 2) {
|
||
11 years ago
|
String key = metadata.getKey();
|
||
11 years ago
|
String name = metadata.getValue(Metadata.VALUE1);
|
||
|
String uuid = metadata.getValue(Metadata.VALUE2);
|
||
11 years ago
|
long deletionDate = metadata.getDeletionDate();
|
||
11 years ago
|
// UUID is uniquely for every TagData, so we don't need to test the name
|
||
10 years ago
|
TagData tagData = tagDataDao.getByUuid(uuid, TagData.ID);
|
||
|
//If you sync with Google tasks it adds some Google task metadata.
|
||
|
//For this metadata we don't create a list!
|
||
|
if(key.equals(TaskToTagMetadata.KEY) && tagData == null && deletionDate == 0) {
|
||
|
tagdata.clear();
|
||
|
tagdata.setId(TagData.NO_ID);
|
||
|
tagdata.setUuid(uuid);
|
||
|
tagdata.setName(name);
|
||
|
tagDataDao.persist(tagdata);
|
||
11 years ago
|
}
|
||
|
}
|
||
14 years ago
|
}
|
||
|
|
||
|
/**
|
||
|
* Turn a model into xml attributes
|
||
|
*/
|
||
8 years ago
|
void deserializeModel(AbstractModel model, Property<?>[] properties) {
|
||
14 years ago
|
for(Property<?> property : properties) {
|
||
|
try {
|
||
|
property.accept(xmlReadingVisitor, model);
|
||
|
} catch (Exception e) {
|
||
9 years ago
|
Timber.e(e, e.getMessage());
|
||
14 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private final XmlReadingPropertyVisitor xmlReadingVisitor = new XmlReadingPropertyVisitor();
|
||
|
|
||
|
private class XmlReadingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> {
|
||
13 years ago
|
|
||
14 years ago
|
@Override
|
||
|
public Void visitInteger(Property<Integer> property,
|
||
|
AbstractModel data) {
|
||
|
String value = xpp.getAttributeValue(null, property.name);
|
||
11 years ago
|
if(value != null) {
|
||
13 years ago
|
data.setValue(property, TasksXmlExporter.XML_NULL.equals(value) ?
|
||
13 years ago
|
null : Integer.parseInt(value));
|
||
11 years ago
|
}
|
||
14 years ago
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Void visitLong(Property<Long> property, AbstractModel data) {
|
||
|
String value = xpp.getAttributeValue(null, property.name);
|
||
11 years ago
|
if(value != null) {
|
||
13 years ago
|
data.setValue(property, TasksXmlExporter.XML_NULL.equals(value) ?
|
||
13 years ago
|
null : Long.parseLong(value));
|
||
11 years ago
|
}
|
||
14 years ago
|
return null;
|
||
|
}
|
||
|
|
||
10 years ago
|
@Override
|
||
|
public Void visitDouble(Property<Double> property, AbstractModel data) {
|
||
|
String value = xpp.getAttributeValue(null, property.name);
|
||
|
if (value != null) {
|
||
|
data.setValue(property, TasksXmlExporter.XML_NULL.equals(value) ?
|
||
|
null : Double.parseDouble(value));
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
14 years ago
|
@Override
|
||
|
public Void visitString(Property<String> property,
|
||
|
AbstractModel data) {
|
||
|
String value = xpp.getAttributeValue(null, property.name);
|
||
11 years ago
|
if(value != null) {
|
||
14 years ago
|
data.setValue(property, value);
|
||
11 years ago
|
}
|
||
14 years ago
|
return null;
|
||
|
}
|
||
12 years ago
|
|
||
14 years ago
|
}
|
||
15 years ago
|
}
|
||
|
|
||
11 years ago
|
// =============================================================== FORMAT3
|
||
|
|
||
|
private static final String FORMAT3 = "3"; //$NON-NLS-1$
|
||
|
private class Format3TaskImporter extends Format2TaskImporter {
|
||
|
|
||
|
public Format3TaskImporter(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 {
|
||
11 years ago
|
switch (tag) {
|
||
|
case BackupConstants.TASK_TAG:
|
||
|
parseTask();
|
||
|
break;
|
||
|
case BackupConstants.METADATA_TAG:
|
||
|
parseMetadata(3);
|
||
|
break;
|
||
8 years ago
|
case BackupConstants.COMMENT_TAG:
|
||
|
parseComment();
|
||
|
break;
|
||
11 years ago
|
case BackupConstants.TAGDATA_TAG:
|
||
|
parseTagdata();
|
||
|
break;
|
||
11 years ago
|
}
|
||
|
} catch (Exception e) {
|
||
|
errorCount++;
|
||
9 years ago
|
Timber.e(e, e.getMessage());
|
||
11 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void parseTagdata() {
|
||
|
tagdata.clear();
|
||
|
deserializeModel(tagdata, TagData.PROPERTIES);
|
||
8 years ago
|
if (tagDataDao.getByUuid(tagdata.getUuid()) == null) {
|
||
|
tagDataDao.persist(tagdata);
|
||
|
}
|
||
11 years ago
|
}
|
||
|
}
|
||
15 years ago
|
}
|