Import backup file in two passes

Import non-task data before loading tasks
pull/3565/head
Alex Baker 7 months ago
parent 93674075cb
commit 4c01ab2e66

@ -15,6 +15,8 @@ import com.todoroo.astrid.service.Upgrader.Companion.V12_4
import com.todoroo.astrid.service.Upgrader.Companion.V12_8 import com.todoroo.astrid.service.Upgrader.Companion.V12_8
import com.todoroo.astrid.service.Upgrader.Companion.V6_4 import com.todoroo.astrid.service.Upgrader.Companion.V6_4
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
@ -85,9 +87,37 @@ class TasksJsonImporter @Inject constructor(
handler.post { progressDialog.setMessage(message) } handler.post { progressDialog.setMessage(message) }
} }
suspend fun importTasks(context: Context, backupFile: Uri?, progressDialog: ProgressDialog?): ImportResult { suspend fun importTasks(
context: Context,
backupFile: Uri?,
progressDialog: ProgressDialog?
): ImportResult = withContext(Dispatchers.IO) {
Timber.d("Importing backup file $backupFile") Timber.d("Importing backup file $backupFile")
val handler = Handler(context.mainLooper) try {
val version = importMetadata(context, backupFile)
importTasks(context, backupFile, progressDialog, version)
if (version < Upgrader.V8_2) {
val themeIndex = preferences.getInt(R.string.p_theme_color, 7)
preferences.setInt(
R.string.p_theme_color,
getAndroidColor(context, themeIndex))
}
if (version < Upgrader.V9_6) {
taskMover.migrateLocalTasks()
}
Timber.d("Updating parents")
caldavDao.updateParents()
} catch (e: IOException) {
Timber.e(e)
}
localBroadcastManager.broadcastRefresh()
result
}
private suspend fun importMetadata(
context: Context,
backupFile: Uri?,
): Int {
val `is`: InputStream? = try { val `is`: InputStream? = try {
context.contentResolver.openInputStream(backupFile!!) context.contentResolver.openInputStream(backupFile!!)
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
@ -97,167 +127,192 @@ class TasksJsonImporter @Inject constructor(
val reader = JsonReader(bufferedReader) val reader = JsonReader(bufferedReader)
reader.isLenient = true reader.isLenient = true
val ignoreKeys = ignorePrefs.map { context.getString(it) } val ignoreKeys = ignorePrefs.map { context.getString(it) }
try { reader.beginObject()
reader.beginObject() var version = 0
var version = 0 while (reader.hasNext()) {
while (reader.hasNext()) { when (val name = reader.nextName()) {
when (val name = reader.nextName()) { "version" -> version = reader.nextInt().also { Timber.d("Backup version: $it") }
"version" -> version = reader.nextInt().also { Timber.d("Backup version: $it") } "timestamp" -> reader.nextLong().let { Timber.d("Backup timestamp: $it") }
"timestamp" -> reader.nextLong().let { Timber.d("Backup timestamp: $it") } "data" -> {
"data" -> { reader.beginObject()
reader.beginObject() while (reader.hasNext()) {
while (reader.hasNext()) { when (val element = reader.nextName()) {
when (val element = reader.nextName()) { "places" -> reader.forEach<Place> { place ->
"tasks" -> { if (locationDao.getByUid(place.uid!!) == null) {
reader.forEach<TaskBackup> { backup -> locationDao.insert(
result.taskCount++ place.copy(icon = place.icon.migrateLegacyIcon())
setProgressMessage( )
handler,
progressDialog,
context.getString(R.string.import_progress_read, result.taskCount))
importTask(backup, version)
}
} }
"places" -> reader.forEach<Place> { place -> }
if (locationDao.getByUid(place.uid!!) == null) { "tags" -> reader.forEach<TagData> { tagData ->
locationDao.insert( findTagData(tagData)?.let {
place.copy(icon = place.icon.migrateLegacyIcon()) return@forEach
)
}
} }
"tags" -> reader.forEach<TagData> { tagData -> tagDataDao.insert(
findTagData(tagData)?.let { tagData.copy(
return@forEach color = themeToColor(context, version, tagData.color ?: 0),
} icon = tagData.icon.migrateLegacyIcon(),
tagDataDao.insert(
tagData.copy(
color = themeToColor(context, version, tagData.color ?: 0),
icon = tagData.icon.migrateLegacyIcon(),
)
) )
} )
"filters" -> reader.forEach<Filter> { }
it "filters" -> reader.forEach<Filter> {
.let { it
if (version < Upgrade_13_2.VERSION) .let {
filterCriteriaProvider.rebuildFilter(it) if (version < Upgrade_13_2.VERSION)
else filterCriteriaProvider.rebuildFilter(it)
it else
} it
.let { filter -> }
if (filterDao.getByName(filter.title!!) == null) { .let { filter ->
filterDao.insert( if (filterDao.getByName(filter.title!!) == null) {
filter.copy( filterDao.insert(
color = themeToColor(context, version, filter.color ?: 0), filter.copy(
icon = filter.icon.migrateLegacyIcon(), color = themeToColor(context, version, filter.color ?: 0),
) icon = filter.icon.migrateLegacyIcon(),
) )
} )
} }
}
"caldavAccounts" -> reader.forEach<CaldavAccount> { account ->
if (caldavDao.getAccountByUuid(account.uuid!!) == null) {
caldavDao.insert(account)
} }
}
"caldavAccounts" -> reader.forEach<CaldavAccount> { account ->
if (caldavDao.getAccountByUuid(account.uuid!!) == null) {
caldavDao.insert(account)
} }
"caldavCalendars" -> reader.forEach<CaldavCalendar> { calendar -> }
if (caldavDao.getCalendarByUuid(calendar.uuid!!) == null) { "caldavCalendars" -> reader.forEach<CaldavCalendar> { calendar ->
caldavDao.insert( if (caldavDao.getCalendarByUuid(calendar.uuid!!) == null) {
calendar.copy( caldavDao.insert(
color = themeToColor(context, version, calendar.color), calendar.copy(
icon = calendar.icon.migrateLegacyIcon(), color = themeToColor(context, version, calendar.color),
) icon = calendar.icon.migrateLegacyIcon(),
) )
} )
} }
"taskListMetadata" -> reader.forEach<TaskListMetadata> { tlm -> }
val id = tlm.filter.takeIf { it?.isNotBlank() == true } ?: tlm.tagUuid!! "taskListMetadata" -> reader.forEach<TaskListMetadata> { tlm ->
if (taskListMetadataDao.fetchByTagOrFilter(id) == null) { val id = tlm.filter.takeIf { it?.isNotBlank() == true } ?: tlm.tagUuid!!
taskListMetadataDao.insert(tlm) if (taskListMetadataDao.fetchByTagOrFilter(id) == null) {
} taskListMetadataDao.insert(tlm)
} }
"taskAttachments" -> reader.forEach<TaskAttachment> { attachment -> }
if (taskAttachmentDao.getAttachment(attachment.remoteId) == null) { "taskAttachments" -> reader.forEach<TaskAttachment> { attachment ->
taskAttachmentDao.insert(attachment) if (taskAttachmentDao.getAttachment(attachment.remoteId) == null) {
} taskAttachmentDao.insert(attachment)
} }
"intPrefs" -> }
Json.decodeFromString<Map<String, Integer>>(reader.jsonString()) "intPrefs" ->
.filterNot { (key, _) -> ignoreKeys.contains(key) } Json.decodeFromString<Map<String, Integer>>(reader.jsonString())
.forEach { (k, v) -> preferences.setInt(k, v as Int) } .filterNot { (key, _) -> ignoreKeys.contains(key) }
"longPrefs" -> .forEach { (k, v) -> preferences.setInt(k, v as Int) }
Json.decodeFromString<Map<String, java.lang.Long>>(reader.jsonString()) "longPrefs" ->
.filterNot { (key, _) -> ignoreKeys.contains(key) } Json.decodeFromString<Map<String, java.lang.Long>>(reader.jsonString())
.forEach { (k, v) -> preferences.setLong(k, v as Long)} .filterNot { (key, _) -> ignoreKeys.contains(key) }
"stringPrefs" -> .forEach { (k, v) -> preferences.setLong(k, v as Long)}
Json.decodeFromString<Map<String, String>>(reader.jsonString()) "stringPrefs" ->
.filterNot { (k, _) -> ignoreKeys.contains(k) } Json.decodeFromString<Map<String, String>>(reader.jsonString())
.forEach { (k, v) -> preferences.setString(k, v)} .filterNot { (k, _) -> ignoreKeys.contains(k) }
"boolPrefs" -> .forEach { (k, v) -> preferences.setString(k, v)}
Json.decodeFromString<Map<String, java.lang.Boolean>>(reader.jsonString()) "boolPrefs" ->
.filterNot { (k, _) -> ignoreKeys.contains(k) } Json.decodeFromString<Map<String, java.lang.Boolean>>(reader.jsonString())
.forEach { (k, v) -> preferences.setBoolean(k, v as Boolean) } .filterNot { (k, _) -> ignoreKeys.contains(k) }
"setPrefs" -> .forEach { (k, v) -> preferences.setBoolean(k, v as Boolean) }
Json.decodeFromString<Map<String, Set<String>>>(reader.jsonString()) "setPrefs" ->
.filterNot { (k, _) -> ignoreKeys.contains(k) } Json.decodeFromString<Map<String, Set<String>>>(reader.jsonString())
.forEach { (k, v) -> preferences.setStringSet(k, v as HashSet<String>)} .filterNot { (k, _) -> ignoreKeys.contains(k) }
"googleTaskAccounts" -> reader.forEach<GoogleTaskAccount> { googleTaskAccount -> .forEach { (k, v) -> preferences.setStringSet(k, v as HashSet<String>)}
if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) { "googleTaskAccounts" -> reader.forEach<GoogleTaskAccount> { googleTaskAccount ->
caldavDao.insert( if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) {
CaldavAccount( caldavDao.insert(
accountType = TYPE_GOOGLE_TASKS, CaldavAccount(
uuid = googleTaskAccount.account, accountType = TYPE_GOOGLE_TASKS,
name = googleTaskAccount.account, uuid = googleTaskAccount.account,
username = googleTaskAccount.account, name = googleTaskAccount.account,
) username = googleTaskAccount.account,
) )
} )
} }
"googleTaskLists" -> reader.forEach<GoogleTaskList> { googleTaskList -> }
if (caldavDao.getCalendar(googleTaskList.remoteId!!) == null) { "googleTaskLists" -> reader.forEach<GoogleTaskList> { googleTaskList ->
caldavDao.insert( if (caldavDao.getCalendar(googleTaskList.remoteId!!) == null) {
CaldavCalendar( caldavDao.insert(
account = googleTaskList.account, CaldavCalendar(
uuid = googleTaskList.remoteId, account = googleTaskList.account,
color = themeToColor(context, version, googleTaskList.color ?: 0), uuid = googleTaskList.remoteId,
icon = googleTaskList.icon?.toString().migrateLegacyIcon(), color = themeToColor(context, version, googleTaskList.color ?: 0),
) icon = googleTaskList.icon?.toString().migrateLegacyIcon(),
) )
} )
}
else -> {
Timber.w("Skipping $element")
reader.skipValue()
} }
} }
else -> {
Timber.w("Skipping $element")
reader.skipValue()
}
} }
reader.endObject()
}
else -> {
Timber.w("Skipping $name")
reader.skipValue()
} }
reader.endObject()
}
else -> {
Timber.w("Skipping $name")
reader.skipValue()
} }
} }
if (version < Upgrader.V8_2) { }
val themeIndex = preferences.getInt(R.string.p_theme_color, 7) reader.close()
preferences.setInt( bufferedReader.close()
R.string.p_theme_color, `is`.close()
getAndroidColor(context, themeIndex)) return version
} }
if (version < Upgrader.V9_6) {
taskMover.migrateLocalTasks() private suspend fun importTasks(
context: Context,
backupFile: Uri?,
progressDialog: ProgressDialog?,
version: Int,
) {
val handler = Handler(context.mainLooper)
val `is`: InputStream? = try {
context.contentResolver.openInputStream(backupFile!!)
} catch (e: FileNotFoundException) {
throw IllegalStateException(e)
}
val bufferedReader = `is`!!.bufferedReader()
val reader = JsonReader(bufferedReader)
reader.isLenient = true
reader.beginObject()
while (reader.hasNext()) {
when (val name = reader.nextName()) {
"data" -> {
reader.beginObject()
while (reader.hasNext()) {
when (val element = reader.nextName()) {
"tasks" -> {
reader.forEach<TaskBackup> { backup ->
result.taskCount++
setProgressMessage(
handler,
progressDialog,
context.getString(R.string.import_progress_read, result.taskCount))
importTask(backup, version)
}
}
else -> {
Timber.w("Skipping $element")
reader.skipValue()
}
}
}
reader.endObject()
}
else -> {
Timber.w("Skipping $name")
reader.skipValue()
}
} }
Timber.d("Updating parents")
caldavDao.updateParents()
reader.close()
bufferedReader.close()
`is`!!.close()
} catch (e: IOException) {
Timber.e(e)
} }
localBroadcastManager.broadcastRefresh() reader.close()
return result bufferedReader.close()
`is`.close()
} }
private suspend fun importTask(backup: TaskBackup, version: Int) { private suspend fun importTask(backup: TaskBackup, version: Int) {

Loading…
Cancel
Save