From 91a99429aea52bedd15d2d7558b3a87b2f6a7a0f Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 14 Jul 2020 14:23:23 -0500 Subject: [PATCH] Use non-blocking daos in xml importer --- .../astrid/backup/TasksXmlImporter.java | 343 ------------------ .../todoroo/astrid/backup/TasksXmlImporter.kt | 276 ++++++++++++++ .../com/todoroo/astrid/service/TaskMover.kt | 2 +- .../org/tasks/dialogs/ImportTasksDialog.kt | 21 +- 4 files changed, 287 insertions(+), 355 deletions(-) delete mode 100755 app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java create mode 100755 app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.kt diff --git a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java deleted file mode 100755 index 064c4fe7d..000000000 --- a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ - -package com.todoroo.astrid.backup; - -import static org.tasks.data.Place.newPlace; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Handler; -import android.text.TextUtils; -import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.astrid.dao.TaskDaoBlocking; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.service.TaskMover; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.R; -import org.tasks.analytics.Firebase; -import org.tasks.backup.XmlReader; -import org.tasks.data.Alarm; -import org.tasks.data.AlarmDaoBlocking; -import org.tasks.data.Geofence; -import org.tasks.data.GoogleTask; -import org.tasks.data.GoogleTaskDaoBlocking; -import org.tasks.data.LocationDaoBlocking; -import org.tasks.data.Place; -import org.tasks.data.Tag; -import org.tasks.data.TagDaoBlocking; -import org.tasks.data.TagData; -import org.tasks.data.TagDataDaoBlocking; -import org.tasks.data.UserActivity; -import org.tasks.data.UserActivityDaoBlocking; -import org.tasks.dialogs.DialogBuilder; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; -import timber.log.Timber; - -public class TasksXmlImporter { - - private static final String FORMAT2 = "2"; // $NON-NLS-1$ - private static final String FORMAT3 = "3"; // $NON-NLS-1$ - private final TagDataDaoBlocking tagDataDao; - private final UserActivityDaoBlocking userActivityDao; - private final DialogBuilder dialogBuilder; - private final TaskDaoBlocking taskDao; - private final LocalBroadcastManager localBroadcastManager; - private final AlarmDaoBlocking alarmDao; - private final TagDaoBlocking tagDao; - private final GoogleTaskDaoBlocking googleTaskDao; - private final TaskMover taskMover; - private final Firebase firebase; - private final LocationDaoBlocking 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 Uri input; - - @Inject - public TasksXmlImporter( - TagDataDaoBlocking tagDataDao, - UserActivityDaoBlocking userActivityDao, - DialogBuilder dialogBuilder, - TaskDaoBlocking taskDao, - LocationDaoBlocking locationDao, - LocalBroadcastManager localBroadcastManager, - AlarmDaoBlocking alarmDao, - TagDaoBlocking tagDao, - GoogleTaskDaoBlocking googleTaskDao, - TaskMover taskMover, - Firebase firebase) { - 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.taskMover = taskMover; - this.firebase = firebase; - } - - private void setProgressMessage(final String message) { - handler.post(() -> progressDialog.setMessage(message)); - } - - public void importTasks(Activity activity, Uri input, ProgressDialog progressDialog) { - this.activity = activity; - this.input = input; - this.progressDialog = progressDialog; - - handler = new Handler(); - - new Thread( - () -> { - try { - performImport(); - taskMover.migrateLocalTasks(); - //noinspection unchecked - firebase.logEvent(R.string.event_xml_import); - } catch (IOException | XmlPullParserException e) { - firebase.reportException(e); - } - }) - .start(); - } - - // --- importers - - // =============================================================== FORMAT2 - - private void performImport() throws IOException, XmlPullParserException { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - XmlPullParser xpp = factory.newPullParser(); - InputStream inputStream = activity.getContentResolver().openInputStream(input); - InputStreamReader reader = new InputStreamReader(inputStream); - xpp.setInput(reader); - - try { - while (xpp.next() != XmlPullParser.END_DOCUMENT) { - String tag = xpp.getName(); - if (xpp.getEventType() == XmlPullParser.END_TAG) { - // Ignore end tags - continue; - } - if (tag != null) { - // Process - if (tag.equals(BackupConstants.ASTRID_TAG)) { - String format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT); - if (TextUtils.equals(format, FORMAT2)) { - new Format2TaskImporter(xpp); - } else if (TextUtils.equals(format, FORMAT3)) { - new Format3TaskImporter(xpp); - } else { - throw new UnsupportedOperationException( - "Did not know how to import tasks with xml format '" + format + "'"); - } - } - } - } - } finally { - reader.close(); - inputStream.close(); - localBroadcastManager.broadcastRefresh(); - handler.post( - () -> { - if (progressDialog.isShowing()) { - DialogUtilities.dismissDialog(activity, progressDialog); - showSummary(); - } - }); - } - } - - private void showSummary() { - Resources r = activity.getResources(); - dialogBuilder - .newDialog(R.string.import_summary_title) - .setMessage( - activity.getString( - R.string.import_summary_message, - "", - 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(); - } - - // =============================================================== FORMAT3 - - private class Format2TaskImporter { - - XmlPullParser xpp; - Task currentTask; - - Format2TaskImporter() {} - - Format2TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { - this.xpp = xpp; - - while (xpp.next() != XmlPullParser.END_DOCUMENT) { - String tag = xpp.getName(); - if (tag == null || xpp.getEventType() == XmlPullParser.END_TAG) { - continue; - } - - try { - switch (tag) { - case BackupConstants.TASK_TAG: - parseTask(); - break; - case BackupConstants.COMMENT_TAG: - parseComment(); - break; - case BackupConstants.METADATA_TAG: - parseMetadata(2); - break; - } - } catch (Exception e) { - errorCount++; - Timber.e(e); - } - } - } - - void parseTask() { - taskCount++; - setProgressMessage(activity.getString(R.string.import_progress_read, taskCount)); - - currentTask = new Task(new XmlReader(xpp)); - - Task existingTask = taskDao.fetch(currentTask.getUuid()); - - if (existingTask == null) { - taskDao.createNew(currentTask); - importCount++; - } else { - skipCount++; - } - } - - /** Imports a comment from the XML we're reading. taken from EditNoteActivity.addComment() */ - void parseComment() { - if (!currentTask.isSaved()) { - return; - } - - UserActivity userActivity = new UserActivity(new XmlReader(xpp)); - userActivityDao.createNew(userActivity); - } - - void parseMetadata(int format) { - if (!currentTask.isSaved()) { - return; - } - XmlReader xml = new XmlReader(xpp); - String key = xml.readString("key"); - if ("alarm".equals(key)) { - Alarm alarm = new Alarm(); - alarm.setTask(currentTask.getId()); - alarm.setTime(xml.readLong("value")); - alarmDao.insert(alarm); - } else if ("geofence".equals(key)) { - Place place = newPlace(); - place.setName(xml.readString("value")); - place.setLatitude(xml.readDouble("value2")); - place.setLongitude(xml.readDouble("value3")); - locationDao.insert(place); - Geofence geofence = new Geofence(); - geofence.setTask(currentTask.getId()); - geofence.setPlace(place.getUid()); - geofence.setRadius(xml.readInteger("value4")); - geofence.setArrival(true); - locationDao.insert(geofence); - } else if ("tags-tag".equals(key)) { - String name = xml.readString("value"); - String tagUid = xml.readString("value2"); - if (tagDao.getTagByTaskAndTagUid(currentTask.getId(), tagUid) == null) { - tagDao.insert(new Tag(currentTask, name, tagUid)); - } - // Construct the TagData from Metadata - // Fix for failed backup, Version before 4.6.10 - if (format == 2) { - TagData tagData = tagDataDao.getByUuid(tagUid); - if (tagData == null) { - tagData = new TagData(); - tagData.setRemoteId(tagUid); - tagData.setName(name); - tagDataDao.createNew(tagData); - } - } - } else if ("gtasks".equals(key)) { - GoogleTask googleTask = new GoogleTask(); - googleTask.setTask(currentTask.getId()); - googleTask.setRemoteId(xml.readString("value")); - googleTask.setListId(xml.readString("value2")); - googleTask.setParent(xml.readLong("value3")); - googleTask.setOrder(xml.readLong("value5")); - googleTask.setRemoteOrder(xml.readLong("value6")); - googleTask.setLastSync(xml.readLong("value7")); - googleTask.setDeleted(xml.readLong("deleted")); - googleTaskDao.insert(googleTask); - } - } - } - - private class Format3TaskImporter extends Format2TaskImporter { - - 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 { - switch (tag) { - case BackupConstants.TASK_TAG: - parseTask(); - break; - case BackupConstants.METADATA_TAG: - parseMetadata(3); - break; - case BackupConstants.COMMENT_TAG: - parseComment(); - break; - case BackupConstants.TAGDATA_TAG: - parseTagdata(); - break; - } - } catch (Exception e) { - errorCount++; - Timber.e(e); - } - } - } - - private void parseTagdata() { - TagData tagData = new TagData(new XmlReader(xpp)); - if (tagDataDao.getByUuid(tagData.getRemoteId()) == null) { - tagDataDao.createNew(tagData); - } - } - } -} diff --git a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.kt b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.kt new file mode 100755 index 000000000..9609df514 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.kt @@ -0,0 +1,276 @@ +/* + * 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.ProgressDialog +import android.content.DialogInterface +import android.net.Uri +import android.os.Handler +import android.text.TextUtils +import com.todoroo.andlib.utility.DialogUtilities +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.service.TaskMover +import org.tasks.LocalBroadcastManager +import org.tasks.R +import org.tasks.analytics.Firebase +import org.tasks.backup.XmlReader +import org.tasks.data.* +import org.tasks.data.Place.Companion.newPlace +import org.tasks.dialogs.DialogBuilder +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory +import timber.log.Timber +import java.io.IOException +import java.io.InputStreamReader +import javax.inject.Inject + +class TasksXmlImporter @Inject constructor( + private val tagDataDao: TagDataDao, + private val userActivityDao: UserActivityDao, + private val dialogBuilder: DialogBuilder, + private val taskDao: TaskDao, + private val locationDao: LocationDao, + private val localBroadcastManager: LocalBroadcastManager, + private val alarmDao: AlarmDao, + private val tagDao: TagDao, + private val googleTaskDao: GoogleTaskDao, + private val taskMover: TaskMover, + private val firebase: Firebase) { + + private var activity: Activity? = null + private var handler: Handler? = null + private var taskCount = 0 + private var importCount = 0 + private var skipCount = 0 + private var errorCount = 0 + private var progressDialog: ProgressDialog? = null + private var input: Uri? = null + + private fun setProgressMessage(message: String) { + handler!!.post { progressDialog!!.setMessage(message) } + } + + suspend fun importTasks(activity: Activity?, input: Uri?, progressDialog: ProgressDialog?) { + this.activity = activity + this.input = input + this.progressDialog = progressDialog + try { + performImport() + taskMover.migrateLocalTasks() + firebase.logEvent(R.string.event_xml_import) + } catch (e: IOException) { + firebase.reportException(e) + } catch (e: XmlPullParserException) { + firebase.reportException(e) + } + } + + // --- importers + // =============================================================== FORMAT2 + @Throws(IOException::class, XmlPullParserException::class) + private suspend fun performImport() { + val factory = XmlPullParserFactory.newInstance() + val xpp = factory.newPullParser() + val inputStream = activity!!.contentResolver.openInputStream(input!!) + val reader = InputStreamReader(inputStream) + xpp.setInput(reader) + try { + while (xpp.next() != XmlPullParser.END_DOCUMENT) { + val tag = xpp.name + if (xpp.eventType == XmlPullParser.END_TAG) { + // Ignore end tags + continue + } + if (tag != null) { + // Process + if (tag == BackupConstants.ASTRID_TAG) { + val format = xpp.getAttributeValue(null, BackupConstants.ASTRID_ATTR_FORMAT) + when { + TextUtils.equals(format, FORMAT2) -> Format2TaskImporter(xpp).process() + TextUtils.equals(format, FORMAT3) -> Format3TaskImporter(xpp).process() + else -> throw UnsupportedOperationException( + "Did not know how to import tasks with xml format '$format'") + } + } + } + } + } finally { + reader.close() + inputStream!!.close() + localBroadcastManager.broadcastRefresh() + handler!!.post { + if (progressDialog!!.isShowing) { + DialogUtilities.dismissDialog(activity, progressDialog) + showSummary() + } + } + } + } + + private fun showSummary() { + val r = activity!!.resources + dialogBuilder + .newDialog(R.string.import_summary_title) + .setMessage( + activity!!.getString( + R.string.import_summary_message, + "", + 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: DialogInterface, id: Int -> dialog.dismiss() } + .show() + } + + // =============================================================== FORMAT3 + private open inner class Format2TaskImporter { + var xpp: XmlPullParser? = null + var currentTask: Task? = null + + internal constructor() + + internal constructor(xpp: XmlPullParser) { + this.xpp = xpp + } + + open suspend fun process() { + while (xpp?.next() != XmlPullParser.END_DOCUMENT) { + val tag = xpp?.name + if (tag == null || xpp?.eventType == XmlPullParser.END_TAG) { + continue + } + try { + when (tag) { + BackupConstants.TASK_TAG -> parseTask() + BackupConstants.COMMENT_TAG -> parseComment() + BackupConstants.METADATA_TAG -> parseMetadata(2) + } + } catch (e: Exception) { + errorCount++ + Timber.e(e) + } + } + } + + suspend fun parseTask() { + taskCount++ + setProgressMessage(activity!!.getString(R.string.import_progress_read, taskCount)) + currentTask = Task(XmlReader(xpp)) + val existingTask = taskDao.fetch(currentTask!!.uuid) + if (existingTask == null) { + taskDao.createNew(currentTask!!) + importCount++ + } else { + skipCount++ + } + } + + /** Imports a comment from the XML we're reading. taken from EditNoteActivity.addComment() */ + suspend fun parseComment() { + if (!currentTask!!.isSaved) { + return + } + val userActivity = UserActivity(XmlReader(xpp)) + userActivityDao.createNew(userActivity) + } + + suspend fun parseMetadata(format: Int) { + if (!currentTask!!.isSaved) { + return + } + val xml = XmlReader(xpp) + val key = xml.readString("key") + if ("alarm" == key) { + val alarm = Alarm() + alarm.task = currentTask!!.id + alarm.time = xml.readLong("value") + alarmDao.insert(alarm) + } else if ("geofence" == key) { + val place = newPlace() + place.name = xml.readString("value") + place.latitude = xml.readDouble("value2") + place.longitude = xml.readDouble("value3") + locationDao.insert(place) + val geofence = Geofence() + geofence.task = currentTask!!.id + geofence.place = place.uid + geofence.radius = xml.readInteger("value4") + geofence.isArrival = true + locationDao.insert(geofence) + } else if ("tags-tag" == key) { + val name = xml.readString("value") + val tagUid = xml.readString("value2") + if (tagDao.getTagByTaskAndTagUid(currentTask!!.id, tagUid) == null) { + tagDao.insert(Tag(currentTask!!, name, tagUid)) + } + // Construct the TagData from Metadata + // Fix for failed backup, Version before 4.6.10 + if (format == 2) { + var tagData = tagDataDao.getByUuid(tagUid) + if (tagData == null) { + tagData = TagData() + tagData.remoteId = tagUid + tagData.name = name + tagDataDao.createNew(tagData) + } + } + } else if ("gtasks" == key) { + val googleTask = GoogleTask() + googleTask.task = currentTask!!.id + googleTask.remoteId = xml.readString("value") + googleTask.listId = xml.readString("value2") + googleTask.parent = xml.readLong("value3") + googleTask.order = xml.readLong("value5") + googleTask.remoteOrder = xml.readLong("value6") + googleTask.lastSync = xml.readLong("value7") + googleTask.deleted = xml.readLong("deleted") + googleTaskDao.insert(googleTask) + } + } + } + + private inner class Format3TaskImporter internal constructor(xpp: XmlPullParser) : Format2TaskImporter() { + private suspend fun parseTagdata() { + val tagData = TagData(XmlReader(xpp)) + if (tagDataDao.getByUuid(tagData.remoteId!!) == null) { + tagDataDao.createNew(tagData) + } + } + + init { + this.xpp = xpp + } + + override suspend fun process() { + while (xpp?.next() != XmlPullParser.END_DOCUMENT) { + val tag = xpp?.name + if (tag == null || xpp?.eventType == XmlPullParser.END_TAG) { + continue + } + try { + when (tag) { + BackupConstants.TASK_TAG -> parseTask() + BackupConstants.METADATA_TAG -> parseMetadata(3) + BackupConstants.COMMENT_TAG -> parseComment() + BackupConstants.TAGDATA_TAG -> parseTagdata() + } + } catch (e: Exception) { + errorCount++ + Timber.e(e) + } + } + } + } + + companion object { + private const val FORMAT2 = "2" // $NON-NLS-1$ + private const val FORMAT3 = "3" // $NON-NLS-1$ + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskMover.kt b/app/src/main/java/com/todoroo/astrid/service/TaskMover.kt index f317639cf..e8de224d4 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskMover.kt +++ b/app/src/main/java/com/todoroo/astrid/service/TaskMover.kt @@ -57,7 +57,7 @@ class TaskMover @Inject constructor( localBroadcastManager.broadcastRefresh() } - fun migrateLocalTasks() = runBlocking { + suspend fun migrateLocalTasks() { val list = caldavDao.getLocalList(context) move(taskDao.getLocalTasks(), CaldavFilter(list)) } diff --git a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.kt b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.kt index 0dce77597..2d0858606 100644 --- a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.kt +++ b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.kt @@ -37,19 +37,18 @@ class ImportTasksDialog : DialogFragment() { progressDialog.show() isCancelable = false when (extension) { - "json" -> { - val activity = requireActivity() - lifecycleScope.launch { - val result = withContext(NonCancellable) { - jsonImporter.importTasks(activity, data, progressDialog) - } - if (progressDialog.isShowing) { - progressDialog.dismiss() - } - showSummary(result) + "json" -> lifecycleScope.launch { + val result = withContext(NonCancellable) { + jsonImporter.importTasks(requireActivity(), data, progressDialog) } + if (progressDialog.isShowing) { + progressDialog.dismiss() + } + showSummary(result) + } + "xml" -> lifecycleScope.launch { + xmlImporter.importTasks(activity, data, progressDialog) } - "xml" -> xmlImporter.importTasks(activity, data, progressDialog) else -> throw RuntimeException("Invalid extension: $extension") } return progressDialog