diff --git a/app/build.gradle b/app/build.gradle index cce1bec87..0552d4872 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,6 +126,7 @@ dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' 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.nononsenseapps:filepicker:4.1.0' compile "com.android.support:design:${SUPPORT_VERSION}" diff --git a/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java b/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java index b111fdcfa..3f7bb0b0c 100644 --- a/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java +++ b/app/src/androidTest/java/org/tasks/jobs/BackupServiceTests.java @@ -8,7 +8,7 @@ package org.tasks.jobs; import android.support.test.runner.AndroidJUnit4; 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.data.Task; @@ -42,7 +42,7 @@ public class BackupServiceTests extends InjectingTestCase { File temporaryDirectory = null; - @Inject TasksXmlExporter xmlExporter; + @Inject TasksJsonExporter xmlExporter; @Inject TaskDao taskDao; @Inject Preferences preferences; @@ -88,7 +88,7 @@ public class BackupServiceTests extends InjectingTestCase { public void testBackup() { assertEquals(0, temporaryDirectory.list().length); - preferences.setLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0); + preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0); // create a backup 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)); // assert summary updated - assertTrue(preferences.getLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0) > 0); + assertTrue(preferences.getLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0) > 0); } @Test diff --git a/app/src/main/java/com/todoroo/astrid/backup/BackupConstants.java b/app/src/main/java/com/todoroo/astrid/backup/BackupConstants.java index 8f6566ef2..ba19ea192 100755 --- a/app/src/main/java/com/todoroo/astrid/backup/BackupConstants.java +++ b/app/src/main/java/com/todoroo/astrid/backup/BackupConstants.java @@ -11,7 +11,7 @@ package com.todoroo.astrid.backup; * @author Tim Su * */ -class BackupConstants { +public class BackupConstants { // 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 */ 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 */ public static final String TAGDATA_TAG = "tagdata"; // --- general - public static final String XML_ENCODING = "utf-8"; - - public static final String EXPORT_FILE_NAME = "user.%s.xml"; + public static final String EXPORT_FILE_NAME = "user.%s.json"; - 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"; } diff --git a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java index 21b936b75..8e75319fa 100755 --- a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java +++ b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java @@ -51,7 +51,7 @@ public class TasksXmlImporter { private final LocalBroadcastManager localBroadcastManager; private final AlarmDao alarmDao; private final TagDao tagDao; - private GoogleTaskDao googleTaskDao; + private final GoogleTaskDao googleTaskDao; private final LocationDao locationDao; private Activity activity; @@ -117,7 +117,7 @@ public class TasksXmlImporter { String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT); if(TextUtils.equals(format, FORMAT2)) { new Format2TaskImporter(xpp); - } else if(TextUtils.equals(format, FORMAT3) || TextUtils.equals(format, FORMAT4)) { + } else if(TextUtils.equals(format, FORMAT3)) { new Format3TaskImporter(xpp); } else { throw new UnsupportedOperationException( @@ -162,8 +162,9 @@ public class TasksXmlImporter { XmlPullParser xpp; Task currentTask; - public Format2TaskImporter() { } - public Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { + Format2TaskImporter() { } + + Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { this.xpp = xpp; while (xpp.next() != XmlPullParser.END_DOCUMENT) { @@ -219,46 +220,6 @@ public class TasksXmlImporter { 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) { if(!currentTask.isSaved()) { return; @@ -314,10 +275,8 @@ public class TasksXmlImporter { // =============================================================== FORMAT3 private static final String FORMAT3 = "3"; //$NON-NLS-1$ - private static final String FORMAT4 = "4"; //$NON-NLS-1$ private class Format3TaskImporter extends Format2TaskImporter { - - public Format3TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { + Format3TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { this.xpp = xpp; while (xpp.next() != XmlPullParser.END_DOCUMENT) { String tag = xpp.getName(); @@ -339,18 +298,6 @@ public class TasksXmlImporter { case BackupConstants.TAGDATA_TAG: parseTagdata(); 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) { errorCount++; diff --git a/app/src/main/java/com/todoroo/astrid/data/Task.java b/app/src/main/java/com/todoroo/astrid/data/Task.java index 8ff86dfc7..9a89aab65 100644 --- a/app/src/main/java/com/todoroo/astrid/data/Task.java +++ b/app/src/main/java/com/todoroo/astrid/data/Task.java @@ -27,7 +27,6 @@ import com.todoroo.andlib.data.Table; import com.todoroo.andlib.utility.DateUtilities; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; import org.tasks.data.Tag; import org.tasks.time.DateTime; @@ -60,7 +59,7 @@ public class Task implements Parcelable { /** ID */ @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - public Long id = NO_ID; + public transient Long id = NO_ID; public static final LongProperty ID = new LongProperty( TABLE, "_id"); @@ -110,8 +109,6 @@ public class Task implements Parcelable { public static final LongProperty DELETION_DATE = new LongProperty( TABLE, "deleted"); - // --- non-core task metadata - @ColumnInfo(name = "notes") public String notes = ""; public static final StringProperty NOTES = new StringProperty( @@ -211,16 +208,16 @@ public class Task implements Parcelable { public static final int IMPORTANCE_NONE = 3; @Ignore - private int googleTaskIndent; + private transient int googleTaskIndent; @Ignore - private String tags; + private transient String tags; @Ignore - private boolean hasFiles; + private transient boolean hasFiles; @Ignore - private HashMap transitoryData = null; + private transient HashMap transitoryData = null; // --- data access boilerplate @@ -373,29 +370,6 @@ public class Task implements Parcelable { 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 public Task(Parcel parcel) { calendarUri = parcel.readString(); diff --git a/app/src/main/java/org/tasks/backup/BackupContainer.java b/app/src/main/java/org/tasks/backup/BackupContainer.java new file mode 100644 index 000000000..4f735afe2 --- /dev/null +++ b/app/src/main/java/org/tasks/backup/BackupContainer.java @@ -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 tasks; + List tags; + List filters; + List googleTaskLists; + + BackupContainer(List tasks, List tags, List filters, List googleTaskLists) { + this.tasks = tasks; + this.tags = tags; + this.filters = filters; + this.googleTaskLists = googleTaskLists; + } + + static class TaskBackup { + Task task; + List alarms; + List locations; + List tags; + List google; + List comments; + + TaskBackup(Task task, List alarms, List locations, List tags, + List google, List comments) { + this.task = task; + this.alarms = alarms; + this.locations = locations; + this.tags = tags; + this.google = google; + this.comments = comments; + } + } +} diff --git a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlExporter.java b/app/src/main/java/org/tasks/backup/TasksJsonExporter.java similarity index 57% rename from app/src/main/java/com/todoroo/astrid/backup/TasksXmlExporter.java rename to app/src/main/java/org/tasks/backup/TasksJsonExporter.java index c3eaf7dfd..05e1d05ef 100755 --- a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlExporter.java +++ b/app/src/main/java/org/tasks/backup/TasksJsonExporter.java @@ -1,44 +1,40 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.backup; +package org.tasks.backup; import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.os.Handler; import android.support.annotation.Nullable; -import android.util.Xml; 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.DialogUtilities; +import com.todoroo.astrid.backup.BackupConstants; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; +import org.tasks.BuildConfig; import org.tasks.R; -import org.tasks.backup.XmlWriter; -import org.tasks.data.Alarm; import org.tasks.data.AlarmDao; -import org.tasks.data.GoogleTask; +import org.tasks.data.FilterDao; import org.tasks.data.GoogleTaskDao; -import org.tasks.data.Location; +import org.tasks.data.GoogleTaskListDao; 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.preferences.Preferences; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.inject.Inject; @@ -46,7 +42,7 @@ import timber.log.Timber; 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$ @@ -65,14 +61,14 @@ public class TasksXmlExporter { private final LocationDao locationDao; private final TagDao tagDao; private final GoogleTaskDao googleTaskDao; + private final FilterDao filterDao; + private final GoogleTaskListDao googleTaskListDao; private final TaskDao taskDao; - private UserActivityDao userActivityDao; + private final UserActivityDao userActivityDao; private final Preferences preferences; - private static final int FORMAT = 4; private Context context; private int exportCount = 0; - private XmlSerializer xml; private ProgressDialog progressDialog; private Handler handler; @@ -93,9 +89,10 @@ public class TasksXmlExporter { } @Inject - public TasksXmlExporter(TagDataDao tagDataDao, TaskDao taskDao, UserActivityDao userActivityDao, - Preferences preferences, AlarmDao alarmDao, LocationDao locationDao, - TagDao tagDao, GoogleTaskDao googleTaskDao) { + public TasksJsonExporter(TagDataDao tagDataDao, TaskDao taskDao, UserActivityDao userActivityDao, + Preferences preferences, AlarmDao alarmDao, LocationDao locationDao, + TagDao tagDao, GoogleTaskDao googleTaskDao, FilterDao filterDao, + GoogleTaskListDao googleTaskListDao) { this.tagDataDao = tagDataDao; this.taskDao = taskDao; this.userActivityDao = userActivityDao; @@ -104,6 +101,8 @@ public class TasksXmlExporter { this.locationDao = locationDao; this.tagDao = tagDao; this.googleTaskDao = googleTaskDao; + this.filterDao = filterDao; + this.googleTaskListDao = googleTaskListDao; } 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 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 tasks) throws IOException { - int length = tasks.size(); - for(int i = 0; i < length; i++) { - Task task = tasks.get(i); - setProgress(i, length); - - xml.startTag(null, BackupConstants.TASK_TAG); - serializeTask(task); - xml.endTag(null, BackupConstants.TASK_TAG); - this.exportCount++; + List taskBackups = new ArrayList<>(); + + for (Task task : tasks) { + setProgress(taskBackups.size(), tasks.size()); + long taskId = task.getId(); + 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) { - XmlWriter writer = new XmlWriter(xml); - task.writeToXml(writer); - for (Alarm alarm : alarmDao.getAlarms(task.getId())) { - try { - xml.startTag(null, BackupConstants.ALARM_TAG); - alarm.writeToXml(writer); - xml.endTag(null, BackupConstants.ALARM_TAG); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - for (Tag tag : tagDao.getTagsForTask(task.getId())) { - try { - xml.startTag(null, BackupConstants.TAG_TAG); - tag.writeToXml(writer); - xml.endTag(null, BackupConstants.TAG_TAG); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - 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); - } - } + Map data = new HashMap<>(); + data.put("version", BuildConfig.VERSION_CODE); + data.put("timestamp", System.currentTimeMillis()); + data.put("data", new BackupContainer( + taskBackups, + tagDataDao.getAll(), + filterDao.getAll(), + googleTaskListDao.getAll())); + + File file = new File(output); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file); + OutputStreamWriter out = new OutputStreamWriter(fos); + Gson gson = BuildConfig.DEBUG + ? new GsonBuilder().setPrettyPrinting().create() + : new Gson(); + out.write(gson.toJson(data)); + out.close(); + fos.close(); + exportCount = taskBackups.size(); } - public static final String XML_NULL = "null"; //$NON-NLS-1$ - private void onFinishExport(final String outputFile) { post(() -> { if(exportCount == 0) { diff --git a/app/src/main/java/org/tasks/backup/TasksJsonImporter.java b/app/src/main/java/org/tasks/backup/TasksJsonImporter.java new file mode 100644 index 000000000..eb23e4e07 --- /dev/null +++ b/app/src/main/java/org/tasks/backup/TasksJsonImporter.java @@ -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(); + } +} diff --git a/app/src/main/java/org/tasks/backup/XmlReader.java b/app/src/main/java/org/tasks/backup/XmlReader.java index 485b48b91..c71abb0e5 100644 --- a/app/src/main/java/org/tasks/backup/XmlReader.java +++ b/app/src/main/java/org/tasks/backup/XmlReader.java @@ -2,10 +2,10 @@ package org.tasks.backup; import org.xmlpull.v1.XmlPullParser; -import static com.todoroo.astrid.backup.TasksXmlExporter.XML_NULL; - public class XmlReader { + private static final String XML_NULL = "null"; //$NON-NLS-1$ + public interface ValueWriter { void write(T value); } diff --git a/app/src/main/java/org/tasks/backup/XmlWriter.java b/app/src/main/java/org/tasks/backup/XmlWriter.java deleted file mode 100644 index 00356c20a..000000000 --- a/app/src/main/java/org/tasks/backup/XmlWriter.java +++ /dev/null @@ -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); - } - } -} diff --git a/app/src/main/java/org/tasks/data/Alarm.java b/app/src/main/java/org/tasks/data/Alarm.java index 570b6c1d3..a3b1ae473 100644 --- a/app/src/main/java/org/tasks/data/Alarm.java +++ b/app/src/main/java/org/tasks/data/Alarm.java @@ -5,18 +5,15 @@ import android.arch.persistence.room.Entity; import android.arch.persistence.room.Ignore; import android.arch.persistence.room.PrimaryKey; -import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; - @Entity(tableName = "alarms") public class Alarm { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "task") - private long task; + private transient long task; @ColumnInfo(name = "time") private long time; @@ -31,15 +28,6 @@ public class Alarm { 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() { return id; } diff --git a/app/src/main/java/org/tasks/data/Filter.java b/app/src/main/java/org/tasks/data/Filter.java index c5876e45b..f1dc19373 100644 --- a/app/src/main/java/org/tasks/data/Filter.java +++ b/app/src/main/java/org/tasks/data/Filter.java @@ -8,7 +8,7 @@ import android.arch.persistence.room.PrimaryKey; public class Filter { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "title") private String title; diff --git a/app/src/main/java/org/tasks/data/FilterDao.java b/app/src/main/java/org/tasks/data/FilterDao.java index 1acbe7186..4ab49bc5f 100644 --- a/app/src/main/java/org/tasks/data/FilterDao.java +++ b/app/src/main/java/org/tasks/data/FilterDao.java @@ -23,9 +23,15 @@ public interface FilterDao { @Insert(onConflict = OnConflictStrategy.REPLACE) long insertOrUpdate(Filter storeObject); + @Insert + void insert(Filter filter); + @Query("SELECT * FROM filters ORDER BY title ASC") List getFilters(); @Query("SELECT * FROM filters WHERE _id = :id LIMIT 1") Filter getById(long id); + + @Query("SELECT * FROM filters") + List getAll(); } diff --git a/app/src/main/java/org/tasks/data/GoogleTask.java b/app/src/main/java/org/tasks/data/GoogleTask.java index 6ca01f42c..c1e511351 100644 --- a/app/src/main/java/org/tasks/data/GoogleTask.java +++ b/app/src/main/java/org/tasks/data/GoogleTask.java @@ -10,7 +10,6 @@ import com.todoroo.andlib.data.Table; import com.todoroo.andlib.utility.DateUtilities; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; @Entity(tableName = "google_tasks") public class GoogleTask { @@ -25,10 +24,10 @@ public class GoogleTask { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "task") - private long task; + private transient long task; @ColumnInfo(name = "remote_id") private String remoteId = ""; @@ -55,7 +54,7 @@ public class GoogleTask { private long deleted; @Ignore - private boolean suppressSync; + private transient boolean suppressSync; public GoogleTask() { @@ -68,29 +67,6 @@ public class GoogleTask { 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() { return id; } diff --git a/app/src/main/java/org/tasks/data/GoogleTaskList.java b/app/src/main/java/org/tasks/data/GoogleTaskList.java index 3d4789c2e..aff364de5 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskList.java +++ b/app/src/main/java/org/tasks/data/GoogleTaskList.java @@ -11,7 +11,7 @@ import android.os.Parcelable; public class GoogleTaskList implements Parcelable { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "remote_id") private String remoteId; diff --git a/app/src/main/java/org/tasks/data/GoogleTaskListDao.java b/app/src/main/java/org/tasks/data/GoogleTaskListDao.java index b063233bf..9e32ce55b 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskListDao.java +++ b/app/src/main/java/org/tasks/data/GoogleTaskListDao.java @@ -16,9 +16,18 @@ public interface GoogleTaskListDao { @Query("SELECT * FROM google_task_lists WHERE deleted = 0 ORDER BY title ASC") List getActiveLists(); + @Query("SELECT * FROM google_task_lists WHERE remote_id = :remoteId LIMIT 1") + GoogleTaskList getByRemoteId(String remoteId); + + @Query("SELECT * FROM google_task_lists") + List getAll(); + @Insert(onConflict = OnConflictStrategy.REPLACE) long insertOrReplace(GoogleTaskList googleTaskList); + @Insert + void insert(GoogleTaskList googleTaskList); + @Query("DELETE FROM google_task_lists WHERE _id = :id") void deleteById(long id); } diff --git a/app/src/main/java/org/tasks/data/Location.java b/app/src/main/java/org/tasks/data/Location.java index 4c5084d68..75268aa9b 100644 --- a/app/src/main/java/org/tasks/data/Location.java +++ b/app/src/main/java/org/tasks/data/Location.java @@ -8,7 +8,6 @@ import android.os.Parcel; import android.os.Parcelable; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; import java.io.Serializable; @@ -16,10 +15,10 @@ import java.io.Serializable; public class Location implements Serializable, Parcelable { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "task") - private long task; + private transient long task; @ColumnInfo(name = "name") private String name; @@ -55,13 +54,6 @@ public class Location implements Serializable, Parcelable { 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() { return id; } diff --git a/app/src/main/java/org/tasks/data/Tag.java b/app/src/main/java/org/tasks/data/Tag.java index a0f75f595..4e6976697 100644 --- a/app/src/main/java/org/tasks/data/Tag.java +++ b/app/src/main/java/org/tasks/data/Tag.java @@ -9,7 +9,6 @@ import android.support.annotation.NonNull; import com.todoroo.andlib.data.Table; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; @Entity(tableName = "tags") public class Tag { @@ -21,10 +20,10 @@ public class Tag { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private long id; + private transient long id; @ColumnInfo(name = "task") - private long task; + private transient long task; @ColumnInfo(name = "name") private String name; @@ -33,7 +32,7 @@ public class Tag { private String tagUid; @ColumnInfo(name = "task_uid") - private String taskUid; + private transient String taskUid; public Tag() { @@ -54,12 +53,6 @@ public class Tag { 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() { return id; } diff --git a/app/src/main/java/org/tasks/data/TagData.java b/app/src/main/java/org/tasks/data/TagData.java index f1b77186c..629ccddd4 100644 --- a/app/src/main/java/org/tasks/data/TagData.java +++ b/app/src/main/java/org/tasks/data/TagData.java @@ -10,14 +10,13 @@ import android.os.Parcelable; import com.todoroo.astrid.data.Task; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; @Entity(tableName = "tagdata") public final class TagData implements Parcelable { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private Long id; + private transient Long id; @ColumnInfo(name = "remoteId") private String remoteId = Task.NO_UUID; @@ -51,13 +50,6 @@ public final class TagData implements Parcelable { 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() { return id; } diff --git a/app/src/main/java/org/tasks/data/TagDataDao.java b/app/src/main/java/org/tasks/data/TagDataDao.java index 063c746dd..b3e150e2e 100644 --- a/app/src/main/java/org/tasks/data/TagDataDao.java +++ b/app/src/main/java/org/tasks/data/TagDataDao.java @@ -17,9 +17,8 @@ public abstract class TagDataDao { @Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1") public abstract TagData getTagByName(String name); - // TODO: does this need to be ordered? - @Query("SELECT * FROM tagdata ORDER BY _id ASC") - public abstract List allTags(); + @Query("SELECT * FROM tagdata") + public abstract List getAll(); @Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1") public abstract TagData getByUuid(String uuid); diff --git a/app/src/main/java/org/tasks/data/UserActivity.java b/app/src/main/java/org/tasks/data/UserActivity.java index 1789e85fc..ee5e27774 100644 --- a/app/src/main/java/org/tasks/data/UserActivity.java +++ b/app/src/main/java/org/tasks/data/UserActivity.java @@ -13,7 +13,6 @@ import com.todoroo.astrid.data.Task; import org.json.JSONException; import org.json.JSONObject; import org.tasks.backup.XmlReader; -import org.tasks.backup.XmlWriter; import java.io.File; @@ -24,7 +23,7 @@ public class UserActivity implements Parcelable { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") - private Long id; + private transient Long id; @ColumnInfo(name = "remoteId") private String remoteId = Task.NO_UUID; @@ -36,7 +35,7 @@ public class UserActivity implements Parcelable { private String picture = ""; @ColumnInfo(name = "target_id") - private String targetId = Task.NO_UUID; + private transient String targetId = Task.NO_UUID; @ColumnInfo(name = "created_at") private Long created = 0L; @@ -63,14 +62,6 @@ public class UserActivity implements Parcelable { 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() { return id; } diff --git a/app/src/main/java/org/tasks/dialogs/ExportTasksDialog.java b/app/src/main/java/org/tasks/dialogs/ExportTasksDialog.java index 1eb777fce..e8d1cea55 100644 --- a/app/src/main/java/org/tasks/dialogs/ExportTasksDialog.java +++ b/app/src/main/java/org/tasks/dialogs/ExportTasksDialog.java @@ -5,7 +5,7 @@ import android.app.ProgressDialog; import android.os.Bundle; 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.NativeDialogFragmentComponent; @@ -19,7 +19,7 @@ public class ExportTasksDialog extends InjectingNativeDialogFragment { } @Inject DialogBuilder dialogBuilder; - @Inject TasksXmlExporter tasksXmlExporter; + @Inject TasksJsonExporter tasksJsonExporter; private ProgressDialog progressDialog; @@ -34,7 +34,7 @@ public class ExportTasksDialog extends InjectingNativeDialogFragment { progressDialog.show(); setCancelable(false); - tasksXmlExporter.exportTasks(getActivity(), TasksXmlExporter.ExportType.EXPORT_TYPE_MANUAL, progressDialog); + tasksJsonExporter.exportTasks(getActivity(), TasksJsonExporter.ExportType.EXPORT_TYPE_MANUAL, progressDialog); return progressDialog; } diff --git a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java index 0966fb323..34a16c33d 100644 --- a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java +++ b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java @@ -6,6 +6,7 @@ import android.os.Bundle; import com.todoroo.astrid.backup.TasksXmlImporter; +import org.tasks.backup.TasksJsonImporter; import org.tasks.injection.InjectingNativeDialogFragment; import org.tasks.injection.NativeDialogFragmentComponent; @@ -24,6 +25,7 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment { private static final String EXTRA_PATH = "extra_path"; @Inject TasksXmlImporter xmlImporter; + @Inject TasksJsonImporter jsonImporter; @Inject DialogBuilder dialogBuilder; @Override @@ -36,7 +38,11 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment { progressDialog.setIndeterminate(true); progressDialog.show(); setCancelable(false); - xmlImporter.importTasks(getActivity(), path, progressDialog); + if (path.endsWith("\\.xml")) { + xmlImporter.importTasks(getActivity(), path, progressDialog); + } else { + jsonImporter.importTasks(getActivity(), path, progressDialog); + } return progressDialog; } diff --git a/app/src/main/java/org/tasks/jobs/BackupJob.java b/app/src/main/java/org/tasks/jobs/BackupJob.java index 49c5c542d..ad5949957 100644 --- a/app/src/main/java/org/tasks/jobs/BackupJob.java +++ b/app/src/main/java/org/tasks/jobs/BackupJob.java @@ -5,7 +5,7 @@ import android.content.Context; import android.content.Intent; 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.IntentServiceComponent; @@ -35,7 +35,7 @@ public class BackupJob extends MidnightJob { @Inject @ForApplication Context context; @Inject JobManager jobManager; - @Inject TasksXmlExporter tasksXmlExporter; + @Inject TasksJsonExporter tasksJsonExporter; @Inject Preferences preferences; @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.jobManager = jobManager; - this.tasksXmlExporter = tasksXmlExporter; + this.tasksJsonExporter = tasksJsonExporter; this.preferences = preferences; } @@ -68,7 +68,7 @@ public class BackupJob extends MidnightJob { } try { - tasksXmlExporter.exportTasks(context, TasksXmlExporter.ExportType.EXPORT_TYPE_SERVICE, null); + tasksJsonExporter.exportTasks(context, TasksJsonExporter.ExportType.EXPORT_TYPE_SERVICE, null); } catch (Exception e) { Timber.e(e, e.getMessage()); }