New JSON backup format

synthesis
Alex Baker 6 years ago
parent 67f3b3d97f
commit ffc2591605

@ -126,6 +126,7 @@ dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
debugCompile 'com.android.support:multidex:1.0.2' debugCompile 'com.android.support:multidex:1.0.2'
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.github.rey5137:material:1.2.4' compile 'com.github.rey5137:material:1.2.4'
compile 'com.nononsenseapps:filepicker:4.1.0' compile 'com.nononsenseapps:filepicker:4.1.0'
compile "com.android.support:design:${SUPPORT_VERSION}" compile "com.android.support:design:${SUPPORT_VERSION}"

@ -8,7 +8,7 @@ package org.tasks.jobs;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.backup.TasksXmlExporter; import org.tasks.backup.TasksJsonExporter;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
@ -42,7 +42,7 @@ public class BackupServiceTests extends InjectingTestCase {
File temporaryDirectory = null; File temporaryDirectory = null;
@Inject TasksXmlExporter xmlExporter; @Inject TasksJsonExporter xmlExporter;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject Preferences preferences; @Inject Preferences preferences;
@ -88,7 +88,7 @@ public class BackupServiceTests extends InjectingTestCase {
public void testBackup() { public void testBackup() {
assertEquals(0, temporaryDirectory.list().length); assertEquals(0, temporaryDirectory.list().length);
preferences.setLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0); preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0);
// create a backup // create a backup
BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences); BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences);
@ -102,7 +102,7 @@ public class BackupServiceTests extends InjectingTestCase {
assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX)); assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX));
// assert summary updated // assert summary updated
assertTrue(preferences.getLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0) > 0); assertTrue(preferences.getLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0) > 0);
} }
@Test @Test

@ -11,7 +11,7 @@ package com.todoroo.astrid.backup;
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */
class BackupConstants { 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
@ -37,24 +37,14 @@ class BackupConstants {
/** Tag containing a metadata item */ /** Tag containing a metadata item */
public static final String METADATA_TAG = "metadata"; public static final String METADATA_TAG = "metadata";
public static final String ALARM_TAG = "alarm";
public static final String LOCATION_TAG = "location";
public static final String TAG_TAG = "tag";
public static final String GOOGLE_TASKS_TAG = "googletasks";
/** Tag containing a tagdata item */ /** Tag containing a tagdata item */
public static final String TAGDATA_TAG = "tagdata"; public static final String TAGDATA_TAG = "tagdata";
// --- general // --- general
public static final String XML_ENCODING = "utf-8"; public static final String EXPORT_FILE_NAME = "user.%s.json";
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.json";
public static final String UPGRADE_FILE_NAME = "upgradefrom.%s.xml"; public static final String UPGRADE_FILE_NAME = "upgradefrom.%s.json";
} }

@ -51,7 +51,7 @@ public class TasksXmlImporter {
private final LocalBroadcastManager localBroadcastManager; private final LocalBroadcastManager localBroadcastManager;
private final AlarmDao alarmDao; private final AlarmDao alarmDao;
private final TagDao tagDao; private final TagDao tagDao;
private GoogleTaskDao googleTaskDao; private final GoogleTaskDao googleTaskDao;
private final LocationDao locationDao; private final LocationDao locationDao;
private Activity activity; private Activity activity;
@ -117,7 +117,7 @@ public class TasksXmlImporter {
String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT); String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT);
if(TextUtils.equals(format, FORMAT2)) { if(TextUtils.equals(format, FORMAT2)) {
new Format2TaskImporter(xpp); new Format2TaskImporter(xpp);
} else if(TextUtils.equals(format, FORMAT3) || TextUtils.equals(format, FORMAT4)) { } else if(TextUtils.equals(format, FORMAT3)) {
new Format3TaskImporter(xpp); new Format3TaskImporter(xpp);
} else { } else {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
@ -162,8 +162,9 @@ public class TasksXmlImporter {
XmlPullParser xpp; XmlPullParser xpp;
Task currentTask; Task currentTask;
public Format2TaskImporter() { } Format2TaskImporter() { }
public Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp; this.xpp = xpp;
while (xpp.next() != XmlPullParser.END_DOCUMENT) { while (xpp.next() != XmlPullParser.END_DOCUMENT) {
@ -219,46 +220,6 @@ public class TasksXmlImporter {
userActivityDao.createNew(userActivity); userActivityDao.createNew(userActivity);
} }
void parseAlarm() {
if (!currentTask.isSaved()) {
return;
}
Alarm alarm = new Alarm(new XmlReader(xpp));
alarm.setTask(currentTask.getId());
alarmDao.insert(alarm);
}
void parseLocation() {
if (!currentTask.isSaved()) {
return;
}
Location location = new Location(new XmlReader(xpp));
location.setTask(currentTask.getId());
locationDao.insert(location);
}
void parseTag() {
if (!currentTask.isSaved()) {
return;
}
Tag tag = new Tag(new XmlReader(xpp));
tag.setTask(currentTask.getId());
tagDao.insert(tag);
}
void parseGoogleTask() {
if (!currentTask.isSaved()) {
return;
}
GoogleTask googleTask = new GoogleTask(new XmlReader(xpp));
googleTask.setTask(currentTask.getId());
googleTaskDao.insert(googleTask);
}
void parseMetadata(int format) { void parseMetadata(int format) {
if(!currentTask.isSaved()) { if(!currentTask.isSaved()) {
return; return;
@ -314,10 +275,8 @@ public class TasksXmlImporter {
// =============================================================== FORMAT3 // =============================================================== FORMAT3
private static final String FORMAT3 = "3"; //$NON-NLS-1$ private static final String FORMAT3 = "3"; //$NON-NLS-1$
private static final String FORMAT4 = "4"; //$NON-NLS-1$
private class Format3TaskImporter extends Format2TaskImporter { private class Format3TaskImporter extends Format2TaskImporter {
Format3TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
public Format3TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException {
this.xpp = xpp; this.xpp = xpp;
while (xpp.next() != XmlPullParser.END_DOCUMENT) { while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName(); String tag = xpp.getName();
@ -339,18 +298,6 @@ public class TasksXmlImporter {
case BackupConstants.TAGDATA_TAG: case BackupConstants.TAGDATA_TAG:
parseTagdata(); parseTagdata();
break; break;
case BackupConstants.ALARM_TAG:
parseAlarm();
break;
case BackupConstants.LOCATION_TAG:
parseLocation();
break;
case BackupConstants.TAG_TAG:
parseTag();
break;
case BackupConstants.GOOGLE_TASKS_TAG:
parseGoogleTask();
break;
} }
} catch (Exception e) { } catch (Exception e) {
errorCount++; errorCount++;

@ -27,7 +27,6 @@ import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
import org.tasks.data.Tag; import org.tasks.data.Tag;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
@ -60,7 +59,7 @@ public class Task implements Parcelable {
/** ID */ /** ID */
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
public Long id = NO_ID; public transient Long id = NO_ID;
public static final LongProperty ID = new LongProperty( public static final LongProperty ID = new LongProperty(
TABLE, "_id"); TABLE, "_id");
@ -110,8 +109,6 @@ public class Task implements Parcelable {
public static final LongProperty DELETION_DATE = new LongProperty( public static final LongProperty DELETION_DATE = new LongProperty(
TABLE, "deleted"); TABLE, "deleted");
// --- non-core task metadata
@ColumnInfo(name = "notes") @ColumnInfo(name = "notes")
public String notes = ""; public String notes = "";
public static final StringProperty NOTES = new StringProperty( public static final StringProperty NOTES = new StringProperty(
@ -211,16 +208,16 @@ public class Task implements Parcelable {
public static final int IMPORTANCE_NONE = 3; public static final int IMPORTANCE_NONE = 3;
@Ignore @Ignore
private int googleTaskIndent; private transient int googleTaskIndent;
@Ignore @Ignore
private String tags; private transient String tags;
@Ignore @Ignore
private boolean hasFiles; private transient boolean hasFiles;
@Ignore @Ignore
private HashMap<String, Object> transitoryData = null; private transient HashMap<String, Object> transitoryData = null;
// --- data access boilerplate // --- data access boilerplate
@ -373,29 +370,6 @@ public class Task implements Parcelable {
remoteId = reader.readString("remoteId"); remoteId = reader.readString("remoteId");
} }
public void writeToXml(XmlWriter writer) {
writer.writeString("calendarUri", calendarUri);
writer.writeLong("completed", completed);
writer.writeLong("created", created);
writer.writeLong("deleted", deleted);
writer.writeLong("dueDate", dueDate);
writer.writeInteger("elapsedSeconds", elapsedSeconds);
writer.writeInteger("estimatedSeconds", estimatedSeconds);
writer.writeLong("hideUntil", hideUntil);
writer.writeInteger("importance", importance);
writer.writeLong("modified", modified);
writer.writeString("notes", notes);
writer.writeString("recurrence", recurrence);
writer.writeInteger("notificationFlags", notificationFlags);
writer.writeLong("lastNotified", lastNotified);
writer.writeLong("notifications", notifications);
writer.writeLong("snoozeTime", snoozeTime);
writer.writeLong("repeatUntil", repeatUntil);
writer.writeLong("timerStart", timerStart);
writer.writeString("title", title);
writer.writeString("remoteId", remoteId);
}
@Ignore @Ignore
public Task(Parcel parcel) { public Task(Parcel parcel) {
calendarUri = parcel.readString(); calendarUri = parcel.readString();

@ -0,0 +1,48 @@
package org.tasks.backup;
import com.todoroo.astrid.data.Task;
import org.tasks.data.Alarm;
import org.tasks.data.Filter;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.Location;
import org.tasks.data.Tag;
import org.tasks.data.TagData;
import org.tasks.data.UserActivity;
import java.util.List;
public class BackupContainer {
List<TaskBackup> tasks;
List<TagData> tags;
List<Filter> filters;
List<GoogleTaskList> googleTaskLists;
BackupContainer(List<TaskBackup> tasks, List<TagData> tags, List<Filter> filters, List<GoogleTaskList> googleTaskLists) {
this.tasks = tasks;
this.tags = tags;
this.filters = filters;
this.googleTaskLists = googleTaskLists;
}
static class TaskBackup {
Task task;
List<Alarm> alarms;
List<Location> locations;
List<Tag> tags;
List<GoogleTask> google;
List<UserActivity> comments;
TaskBackup(Task task, List<Alarm> alarms, List<Location> locations, List<Tag> tags,
List<GoogleTask> google, List<UserActivity> comments) {
this.task = task;
this.alarms = alarms;
this.locations = locations;
this.tags = tags;
this.google = google;
this.comments = comments;
}
}
}

@ -1,44 +1,40 @@
/** package org.tasks.backup;
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.backup;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Xml;
import android.widget.Toast; import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.backup.BackupConstants;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.backup.XmlWriter;
import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao; import org.tasks.data.AlarmDao;
import org.tasks.data.GoogleTask; import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.GoogleTaskDao;
import org.tasks.data.Location; import org.tasks.data.GoogleTaskListDao;
import org.tasks.data.LocationDao; import org.tasks.data.LocationDao;
import org.tasks.data.Tag;
import org.tasks.data.TagDao; import org.tasks.data.TagDao;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao; import org.tasks.data.TagDataDao;
import org.tasks.data.UserActivity;
import org.tasks.data.UserActivityDao; import org.tasks.data.UserActivityDao;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.xmlpull.v1.XmlSerializer;
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.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
@ -46,7 +42,7 @@ import timber.log.Timber;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
public class TasksXmlExporter { public class TasksJsonExporter {
public static final String PREF_BACKUP_LAST_DATE = "backupDate"; //$NON-NLS-1$ public static final String PREF_BACKUP_LAST_DATE = "backupDate"; //$NON-NLS-1$
@ -65,14 +61,14 @@ public class TasksXmlExporter {
private final LocationDao locationDao; private final LocationDao locationDao;
private final TagDao tagDao; private final TagDao tagDao;
private final GoogleTaskDao googleTaskDao; private final GoogleTaskDao googleTaskDao;
private final FilterDao filterDao;
private final GoogleTaskListDao googleTaskListDao;
private final TaskDao taskDao; private final TaskDao taskDao;
private UserActivityDao userActivityDao; private final UserActivityDao userActivityDao;
private final Preferences preferences; private final Preferences preferences;
private static final int FORMAT = 4;
private Context context; private Context context;
private int exportCount = 0; private int exportCount = 0;
private XmlSerializer xml;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private Handler handler; private Handler handler;
@ -93,9 +89,10 @@ public class TasksXmlExporter {
} }
@Inject @Inject
public TasksXmlExporter(TagDataDao tagDataDao, TaskDao taskDao, UserActivityDao userActivityDao, public TasksJsonExporter(TagDataDao tagDataDao, TaskDao taskDao, UserActivityDao userActivityDao,
Preferences preferences, AlarmDao alarmDao, LocationDao locationDao, Preferences preferences, AlarmDao alarmDao, LocationDao locationDao,
TagDao tagDao, GoogleTaskDao googleTaskDao) { TagDao tagDao, GoogleTaskDao googleTaskDao, FilterDao filterDao,
GoogleTaskListDao googleTaskListDao) {
this.tagDataDao = tagDataDao; this.tagDataDao = tagDataDao;
this.taskDao = taskDao; this.taskDao = taskDao;
this.userActivityDao = userActivityDao; this.userActivityDao = userActivityDao;
@ -104,6 +101,8 @@ public class TasksXmlExporter {
this.locationDao = locationDao; this.locationDao = locationDao;
this.tagDao = tagDao; this.tagDao = tagDao;
this.googleTaskDao = googleTaskDao; this.googleTaskDao = googleTaskDao;
this.filterDao = filterDao;
this.googleTaskListDao = googleTaskListDao;
} }
public void exportTasks(final Context context, final ExportType exportType, @Nullable final ProgressDialog progressDialog) { public void exportTasks(final Context context, final ExportType exportType, @Nullable final ProgressDialog progressDialog) {
@ -144,108 +143,43 @@ public class TasksXmlExporter {
} }
private void doTasksExport(String output, List<Task> tasks) throws IOException { private void doTasksExport(String output, List<Task> tasks) throws IOException {
File xmlFile = new File(output);
xmlFile.createNewFile();
FileOutputStream fos = new FileOutputStream(xmlFile);
xml = Xml.newSerializer();
xml.setOutput(fos, BackupConstants.XML_ENCODING);
xml.startDocument(null, null);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, BackupConstants.ASTRID_TAG);
xml.attribute(null, BackupConstants.ASTRID_ATTR_VERSION,
Integer.toString(preferences.getLastSetVersion()));
xml.attribute(null, BackupConstants.ASTRID_ATTR_FORMAT,
Integer.toString(FORMAT));
serializeTasks(tasks);
serializeTagDatas();
xml.endTag(null, BackupConstants.ASTRID_TAG);
xml.endDocument();
xml.flush();
fos.close();
}
private void serializeTagDatas() {
for (TagData tag : tagDataDao.allTags()) {
try {
xml.startTag(null, BackupConstants.TAGDATA_TAG);
tag.writeToXml(new XmlWriter(xml));
xml.endTag(null, BackupConstants.TAGDATA_TAG);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
private void serializeTasks(List<Task> tasks) throws IOException {
int length = tasks.size();
for(int i = 0; i < length; i++) {
Task task = tasks.get(i);
setProgress(i, length); List<BackupContainer.TaskBackup> taskBackups = new ArrayList<>();
xml.startTag(null, BackupConstants.TASK_TAG); for (Task task : tasks) {
serializeTask(task); setProgress(taskBackups.size(), tasks.size());
xml.endTag(null, BackupConstants.TASK_TAG); long taskId = task.getId();
this.exportCount++; taskBackups.add(new BackupContainer.TaskBackup(
task,
alarmDao.getAlarms(taskId),
locationDao.getGeofences(taskId),
tagDao.getTagsForTask(taskId),
googleTaskDao.getAllByTaskId(taskId),
userActivityDao.getCommentsForTask(task.getUuid())));
} }
}
private synchronized void serializeTask(Task task) { Map<String, Object> data = new HashMap<>();
XmlWriter writer = new XmlWriter(xml); data.put("version", BuildConfig.VERSION_CODE);
task.writeToXml(writer); data.put("timestamp", System.currentTimeMillis());
for (Alarm alarm : alarmDao.getAlarms(task.getId())) { data.put("data", new BackupContainer(
try { taskBackups,
xml.startTag(null, BackupConstants.ALARM_TAG); tagDataDao.getAll(),
alarm.writeToXml(writer); filterDao.getAll(),
xml.endTag(null, BackupConstants.ALARM_TAG); googleTaskListDao.getAll()));
} catch (IOException e) {
throw new RuntimeException(e); File file = new File(output);
} file.createNewFile();
} FileOutputStream fos = new FileOutputStream(file);
for (Tag tag : tagDao.getTagsForTask(task.getId())) { OutputStreamWriter out = new OutputStreamWriter(fos);
try { Gson gson = BuildConfig.DEBUG
xml.startTag(null, BackupConstants.TAG_TAG); ? new GsonBuilder().setPrettyPrinting().create()
tag.writeToXml(writer); : new Gson();
xml.endTag(null, BackupConstants.TAG_TAG); out.write(gson.toJson(data));
} catch (IOException e) { out.close();
throw new RuntimeException(e); fos.close();
} exportCount = taskBackups.size();
}
for (Location location : locationDao.getGeofences(task.getId())) {
try {
xml.startTag(null, BackupConstants.LOCATION_TAG);
location.writeToXml(writer);
xml.endTag(null, BackupConstants.LOCATION_TAG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (GoogleTask googleTask : googleTaskDao.getAllByTaskId(task.getId())) {
try {
xml.startTag(null, BackupConstants.GOOGLE_TASKS_TAG);
googleTask.writeToXml(writer);
xml.endTag(null, BackupConstants.GOOGLE_TASKS_TAG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (UserActivity comment : userActivityDao.getCommentsForTask(task.getUuid())) {
try {
xml.startTag(null, BackupConstants.COMMENT_TAG);
comment.writeToXml(new XmlWriter(xml));
xml.endTag(null, BackupConstants.COMMENT_TAG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} }
public static final String XML_NULL = "null"; //$NON-NLS-1$
private void onFinishExport(final String outputFile) { private void onFinishExport(final String outputFile) {
post(() -> { post(() -> {
if(exportCount == 0) { if(exportCount == 0) {

@ -0,0 +1,189 @@
package org.tasks.backup;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.res.Resources;
import android.os.Handler;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao;
import org.tasks.data.Filter;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
import org.tasks.data.UserActivity;
import org.tasks.data.UserActivityDao;
import org.tasks.dialogs.DialogBuilder;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileReader;
import java.io.IOException;
import javax.inject.Inject;
import timber.log.Timber;
public class TasksJsonImporter {
private final TagDataDao tagDataDao;
private final UserActivityDao userActivityDao;
private final DialogBuilder dialogBuilder;
private final TaskDao taskDao;
private final LocalBroadcastManager localBroadcastManager;
private final AlarmDao alarmDao;
private final TagDao tagDao;
private final GoogleTaskDao googleTaskDao;
private final GoogleTaskListDao googleTaskListDao;
private final FilterDao filterDao;
private final LocationDao locationDao;
private Activity activity;
private Handler handler;
private int taskCount;
private int importCount = 0;
private int skipCount = 0;
private int errorCount = 0;
private ProgressDialog progressDialog;
private String input;
private void setProgressMessage(final String message) {
handler.post(() -> progressDialog.setMessage(message));
}
@Inject
public TasksJsonImporter(TagDataDao tagDataDao, UserActivityDao userActivityDao,
DialogBuilder dialogBuilder, TaskDao taskDao, LocationDao locationDao,
LocalBroadcastManager localBroadcastManager, AlarmDao alarmDao,
TagDao tagDao, GoogleTaskDao googleTaskDao, GoogleTaskListDao googleTaskListDao,
FilterDao filterDao) {
this.tagDataDao = tagDataDao;
this.userActivityDao = userActivityDao;
this.dialogBuilder = dialogBuilder;
this.taskDao = taskDao;
this.locationDao = locationDao;
this.localBroadcastManager = localBroadcastManager;
this.alarmDao = alarmDao;
this.tagDao = tagDao;
this.googleTaskDao = googleTaskDao;
this.googleTaskListDao = googleTaskListDao;
this.filterDao = filterDao;
}
public void importTasks(Activity activity, String input, ProgressDialog progressDialog) {
this.activity = activity;
this.input = input;
this.progressDialog = progressDialog;
handler = new Handler();
new Thread(() -> {
try {
performImport();
} catch (IOException | XmlPullParserException e) {
Timber.e(e, e.getMessage());
}
}).start();
}
private void performImport() throws IOException, XmlPullParserException {
FileReader fileReader = new FileReader(input);
String string = CharStreams.toString(fileReader);
fileReader.close();
Gson gson = new Gson();
JsonObject input = gson.fromJson(string, JsonObject.class);
try {
JsonElement data = input.get("data");
BackupContainer backupContainer = gson.fromJson(data, BackupContainer.class);
for (TagData tagData : backupContainer.tags) {
if (tagDataDao.getByUuid(tagData.getRemoteId()) == null) {
tagDataDao.createNew(tagData);
}
}
for (GoogleTaskList googleTaskList : backupContainer.googleTaskLists) {
if (googleTaskListDao.getByRemoteId(googleTaskList.getRemoteId()) == null) {
googleTaskListDao.insert(googleTaskList);
}
}
for (Filter filter : backupContainer.filters) {
if (filterDao.getByName(filter.getTitle()) == null) {
filterDao.insert(filter);
}
}
for (BackupContainer.TaskBackup backup : backupContainer.tasks) {
taskCount++;
setProgressMessage(activity.getString(R.string.import_progress_read, taskCount));
Task task = backup.task;
if (taskDao.fetch(task.getUuid()) != null) {
skipCount++;
continue;
}
taskDao.createNew(task);
long taskId = task.getId();
String taskUuid = task.getUuid();
for (Alarm alarm : backup.alarms) {
alarm.setTask(taskId);
alarmDao.insert(alarm);
}
for (UserActivity comment : backup.comments) {
comment.setTargetId(taskUuid);
userActivityDao.createNew(comment);
}
for (GoogleTask googleTask : backup.google) {
googleTask.setTask(taskId);
googleTaskDao.insert(googleTask);
}
for (Tag tag : backup.tags) {
tag.setTask(taskId);
tag.setTaskUid(taskUuid);
tagDao.insert(tag);
}
for (Location location : backup.locations) {
location.setTask(taskId);
locationDao.insert(location);
}
importCount++;
}
} finally {
localBroadcastManager.broadcastRefresh();
handler.post(() -> {
if(progressDialog.isShowing()) {
DialogUtilities.dismissDialog(activity, progressDialog);
showSummary();
}
});
}
}
private void showSummary() {
Resources r = activity.getResources();
dialogBuilder.newDialog()
.setTitle(R.string.import_summary_title)
.setMessage(activity.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)))
.setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.dismiss())
.show();
}
}

@ -2,10 +2,10 @@ package org.tasks.backup;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import static com.todoroo.astrid.backup.TasksXmlExporter.XML_NULL;
public class XmlReader { public class XmlReader {
private static final String XML_NULL = "null"; //$NON-NLS-1$
public interface ValueWriter<T> { public interface ValueWriter<T> {
void write(T value); void write(T value);
} }

@ -1,66 +0,0 @@
package org.tasks.backup;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import timber.log.Timber;
import static com.todoroo.astrid.backup.TasksXmlExporter.XML_NULL;
public class XmlWriter {
private final XmlSerializer xml;
public XmlWriter(XmlSerializer xml) {
this.xml = xml;
}
public void writeLong(String name, Long value) {
try {
String valueString = (value == null) ? XML_NULL : value.toString();
xml.attribute(null, name, valueString);
} catch (UnsupportedOperationException e) {
// didn't read this value, do nothing
Timber.e(e, e.getMessage());
} catch (IllegalArgumentException | IOException | IllegalStateException e) {
throw new RuntimeException(e);
}
}
public void writeString(String name, String value) {
try {
if(value != null) {
xml.attribute(null, name, value);
}
} catch (UnsupportedOperationException e) {
// didn't read this value, do nothing
Timber.v(e, e.getMessage());
} catch (IllegalArgumentException | IOException | IllegalStateException e) {
throw new RuntimeException(e);
}
}
public void writeInteger(String name, Integer value) {
try {
String valueString = (value == null) ? XML_NULL : value.toString();
xml.attribute(null, name, valueString);
} catch (UnsupportedOperationException e) {
// didn't read this value, do nothing
Timber.e(e, e.getMessage());
} catch (IllegalArgumentException | IOException | IllegalStateException e) {
throw new RuntimeException(e);
}
}
public void writeDouble(String name, Double value) {
try {
String valueString = (value == null) ? XML_NULL : value.toString();
xml.attribute(null, name, valueString);
} catch (UnsupportedOperationException e) {
// didn't read this value, do nothing
Timber.e(e, e.getMessage());
} catch (IllegalArgumentException | IllegalStateException | IOException e) {
throw new RuntimeException(e);
}
}
}

@ -5,18 +5,15 @@ import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore; import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey; import android.arch.persistence.room.PrimaryKey;
import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
@Entity(tableName = "alarms") @Entity(tableName = "alarms")
public class Alarm { public class Alarm {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "task") @ColumnInfo(name = "task")
private long task; private transient long task;
@ColumnInfo(name = "time") @ColumnInfo(name = "time")
private long time; private long time;
@ -31,15 +28,6 @@ public class Alarm {
this.time = time; this.time = time;
} }
@Ignore
public Alarm(XmlReader xml) {
xml.readLong("time", this::setTime);
}
public void writeToXml(XmlWriter writer) {
writer.writeLong("time", time);
}
public long getId() { public long getId() {
return id; return id;
} }

@ -8,7 +8,7 @@ import android.arch.persistence.room.PrimaryKey;
public class Filter { public class Filter {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "title") @ColumnInfo(name = "title")
private String title; private String title;

@ -23,9 +23,15 @@ public interface FilterDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOrUpdate(Filter storeObject); long insertOrUpdate(Filter storeObject);
@Insert
void insert(Filter filter);
@Query("SELECT * FROM filters ORDER BY title ASC") @Query("SELECT * FROM filters ORDER BY title ASC")
List<Filter> getFilters(); List<Filter> getFilters();
@Query("SELECT * FROM filters WHERE _id = :id LIMIT 1") @Query("SELECT * FROM filters WHERE _id = :id LIMIT 1")
Filter getById(long id); Filter getById(long id);
@Query("SELECT * FROM filters")
List<Filter> getAll();
} }

@ -10,7 +10,6 @@ import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
@Entity(tableName = "google_tasks") @Entity(tableName = "google_tasks")
public class GoogleTask { public class GoogleTask {
@ -25,10 +24,10 @@ public class GoogleTask {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "task") @ColumnInfo(name = "task")
private long task; private transient long task;
@ColumnInfo(name = "remote_id") @ColumnInfo(name = "remote_id")
private String remoteId = ""; private String remoteId = "";
@ -55,7 +54,7 @@ public class GoogleTask {
private long deleted; private long deleted;
@Ignore @Ignore
private boolean suppressSync; private transient boolean suppressSync;
public GoogleTask() { public GoogleTask() {
@ -68,29 +67,6 @@ public class GoogleTask {
this.listId = listId; this.listId = listId;
} }
@Ignore
public GoogleTask(XmlReader xml) {
remoteId = xml.readString("remote_id");
listId = xml.readString("list_id");
parent = xml.readLong("parent");
indent = xml.readInteger("indent");
order = xml.readLong("order");
remoteOrder = xml.readLong("remote_order");
lastSync = xml.readLong("last_sync");
deleted = xml.readLong("deleted");
}
public void writeToXml(XmlWriter xml) {
xml.writeString("remote_id", remoteId);
xml.writeString("list_id", listId);
xml.writeLong("parent", parent);
xml.writeInteger("indent", indent);
xml.writeLong("order", order);
xml.writeLong("remote_order", remoteOrder);
xml.writeLong("last_sync", lastSync);
xml.writeLong("deleted", deleted);
}
public long getId() { public long getId() {
return id; return id;
} }

@ -11,7 +11,7 @@ import android.os.Parcelable;
public class GoogleTaskList implements Parcelable { public class GoogleTaskList implements Parcelable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "remote_id") @ColumnInfo(name = "remote_id")
private String remoteId; private String remoteId;

@ -16,9 +16,18 @@ public interface GoogleTaskListDao {
@Query("SELECT * FROM google_task_lists WHERE deleted = 0 ORDER BY title ASC") @Query("SELECT * FROM google_task_lists WHERE deleted = 0 ORDER BY title ASC")
List<GoogleTaskList> getActiveLists(); List<GoogleTaskList> getActiveLists();
@Query("SELECT * FROM google_task_lists WHERE remote_id = :remoteId LIMIT 1")
GoogleTaskList getByRemoteId(String remoteId);
@Query("SELECT * FROM google_task_lists")
List<GoogleTaskList> getAll();
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOrReplace(GoogleTaskList googleTaskList); long insertOrReplace(GoogleTaskList googleTaskList);
@Insert
void insert(GoogleTaskList googleTaskList);
@Query("DELETE FROM google_task_lists WHERE _id = :id") @Query("DELETE FROM google_task_lists WHERE _id = :id")
void deleteById(long id); void deleteById(long id);
} }

@ -8,7 +8,6 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
import java.io.Serializable; import java.io.Serializable;
@ -16,10 +15,10 @@ import java.io.Serializable;
public class Location implements Serializable, Parcelable { public class Location implements Serializable, Parcelable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "task") @ColumnInfo(name = "task")
private long task; private transient long task;
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
private String name; private String name;
@ -55,13 +54,6 @@ public class Location implements Serializable, Parcelable {
xml.readInteger("radius", this::setRadius); xml.readInteger("radius", this::setRadius);
} }
public void writeToXml(XmlWriter xmlWriter) {
xmlWriter.writeString("name", name);
xmlWriter.writeDouble("latitude", latitude);
xmlWriter.writeDouble("longitude", longitude);
xmlWriter.writeInteger("radius", radius);
}
public long getId() { public long getId() {
return id; return id;
} }

@ -9,7 +9,6 @@ import android.support.annotation.NonNull;
import com.todoroo.andlib.data.Table; import com.todoroo.andlib.data.Table;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
@Entity(tableName = "tags") @Entity(tableName = "tags")
public class Tag { public class Tag {
@ -21,10 +20,10 @@ public class Tag {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private transient long id;
@ColumnInfo(name = "task") @ColumnInfo(name = "task")
private long task; private transient long task;
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
private String name; private String name;
@ -33,7 +32,7 @@ public class Tag {
private String tagUid; private String tagUid;
@ColumnInfo(name = "task_uid") @ColumnInfo(name = "task_uid")
private String taskUid; private transient String taskUid;
public Tag() { public Tag() {
@ -54,12 +53,6 @@ public class Tag {
xmlReader.readString("task_uid", this::setTaskUid); xmlReader.readString("task_uid", this::setTaskUid);
} }
public void writeToXml(XmlWriter xmlWriter) {
xmlWriter.writeString("name", name);
xmlWriter.writeString("tag_uid", tagUid);
xmlWriter.writeString("task_uid", taskUid);
}
public long getId() { public long getId() {
return id; return id;
} }

@ -10,14 +10,13 @@ import android.os.Parcelable;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
@Entity(tableName = "tagdata") @Entity(tableName = "tagdata")
public final class TagData implements Parcelable { public final class TagData implements Parcelable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private Long id; private transient Long id;
@ColumnInfo(name = "remoteId") @ColumnInfo(name = "remoteId")
private String remoteId = Task.NO_UUID; private String remoteId = Task.NO_UUID;
@ -51,13 +50,6 @@ public final class TagData implements Parcelable {
tagOrdering = parcel.readString(); tagOrdering = parcel.readString();
} }
public void writeToXml(XmlWriter writer) {
writer.writeString("remoteId", remoteId);
writer.writeString("name", name);
writer.writeInteger("color", color);
writer.writeString("tagOrdering", tagOrdering);
}
public Long getId() { public Long getId() {
return id; return id;
} }

@ -17,9 +17,8 @@ public abstract class TagDataDao {
@Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1") @Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1")
public abstract TagData getTagByName(String name); public abstract TagData getTagByName(String name);
// TODO: does this need to be ordered? @Query("SELECT * FROM tagdata")
@Query("SELECT * FROM tagdata ORDER BY _id ASC") public abstract List<TagData> getAll();
public abstract List<TagData> allTags();
@Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1") @Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1")
public abstract TagData getByUuid(String uuid); public abstract TagData getByUuid(String uuid);

@ -13,7 +13,6 @@ import com.todoroo.astrid.data.Task;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
import java.io.File; import java.io.File;
@ -24,7 +23,7 @@ public class UserActivity implements Parcelable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private Long id; private transient Long id;
@ColumnInfo(name = "remoteId") @ColumnInfo(name = "remoteId")
private String remoteId = Task.NO_UUID; private String remoteId = Task.NO_UUID;
@ -36,7 +35,7 @@ public class UserActivity implements Parcelable {
private String picture = ""; private String picture = "";
@ColumnInfo(name = "target_id") @ColumnInfo(name = "target_id")
private String targetId = Task.NO_UUID; private transient String targetId = Task.NO_UUID;
@ColumnInfo(name = "created_at") @ColumnInfo(name = "created_at")
private Long created = 0L; private Long created = 0L;
@ -63,14 +62,6 @@ public class UserActivity implements Parcelable {
created = parcel.readLong(); created = parcel.readLong();
} }
public void writeToXml(XmlWriter writer) {
writer.writeString("remoteId", remoteId);
writer.writeString("message", message);
writer.writeString("picture", picture);
writer.writeString("target_id", targetId);
writer.writeLong("created_at", created);
}
public Long getId() { public Long getId() {
return id; return id;
} }

@ -5,7 +5,7 @@ import android.app.ProgressDialog;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.todoroo.astrid.backup.TasksXmlExporter; import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.InjectingNativeDialogFragment; import org.tasks.injection.InjectingNativeDialogFragment;
import org.tasks.injection.NativeDialogFragmentComponent; import org.tasks.injection.NativeDialogFragmentComponent;
@ -19,7 +19,7 @@ public class ExportTasksDialog extends InjectingNativeDialogFragment {
} }
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject TasksXmlExporter tasksXmlExporter; @Inject TasksJsonExporter tasksJsonExporter;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
@ -34,7 +34,7 @@ public class ExportTasksDialog extends InjectingNativeDialogFragment {
progressDialog.show(); progressDialog.show();
setCancelable(false); setCancelable(false);
tasksXmlExporter.exportTasks(getActivity(), TasksXmlExporter.ExportType.EXPORT_TYPE_MANUAL, progressDialog); tasksJsonExporter.exportTasks(getActivity(), TasksJsonExporter.ExportType.EXPORT_TYPE_MANUAL, progressDialog);
return progressDialog; return progressDialog;
} }

@ -6,6 +6,7 @@ import android.os.Bundle;
import com.todoroo.astrid.backup.TasksXmlImporter; import com.todoroo.astrid.backup.TasksXmlImporter;
import org.tasks.backup.TasksJsonImporter;
import org.tasks.injection.InjectingNativeDialogFragment; import org.tasks.injection.InjectingNativeDialogFragment;
import org.tasks.injection.NativeDialogFragmentComponent; import org.tasks.injection.NativeDialogFragmentComponent;
@ -24,6 +25,7 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment {
private static final String EXTRA_PATH = "extra_path"; private static final String EXTRA_PATH = "extra_path";
@Inject TasksXmlImporter xmlImporter; @Inject TasksXmlImporter xmlImporter;
@Inject TasksJsonImporter jsonImporter;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Override @Override
@ -36,7 +38,11 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment {
progressDialog.setIndeterminate(true); progressDialog.setIndeterminate(true);
progressDialog.show(); progressDialog.show();
setCancelable(false); setCancelable(false);
xmlImporter.importTasks(getActivity(), path, progressDialog); if (path.endsWith("\\.xml")) {
xmlImporter.importTasks(getActivity(), path, progressDialog);
} else {
jsonImporter.importTasks(getActivity(), path, progressDialog);
}
return progressDialog; return progressDialog;
} }

@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.v4.app.JobIntentService; import android.support.v4.app.JobIntentService;
import com.todoroo.astrid.backup.TasksXmlExporter; import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.IntentServiceComponent; import org.tasks.injection.IntentServiceComponent;
@ -35,7 +35,7 @@ public class BackupJob extends MidnightJob {
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject JobManager jobManager; @Inject JobManager jobManager;
@Inject TasksXmlExporter tasksXmlExporter; @Inject TasksJsonExporter tasksJsonExporter;
@Inject Preferences preferences; @Inject Preferences preferences;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -43,10 +43,10 @@ public class BackupJob extends MidnightJob {
} }
BackupJob(Context context, JobManager jobManager, TasksXmlExporter tasksXmlExporter, Preferences preferences) { BackupJob(Context context, JobManager jobManager, TasksJsonExporter tasksJsonExporter, Preferences preferences) {
this.context = context; this.context = context;
this.jobManager = jobManager; this.jobManager = jobManager;
this.tasksXmlExporter = tasksXmlExporter; this.tasksJsonExporter = tasksJsonExporter;
this.preferences = preferences; this.preferences = preferences;
} }
@ -68,7 +68,7 @@ public class BackupJob extends MidnightJob {
} }
try { try {
tasksXmlExporter.exportTasks(context, TasksXmlExporter.ExportType.EXPORT_TYPE_SERVICE, null); tasksJsonExporter.exportTasks(context, TasksJsonExporter.ExportType.EXPORT_TYPE_SERVICE, null);
} catch (Exception e) { } catch (Exception e) {
Timber.e(e, e.getMessage()); Timber.e(e, e.getMessage());
} }

Loading…
Cancel
Save