|
|
|
|
@ -4,6 +4,7 @@ import android.app.ProgressDialog
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import android.os.Handler
|
|
|
|
|
import android.util.JsonReader
|
|
|
|
|
import com.todoroo.astrid.dao.TaskDao
|
|
|
|
|
import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms
|
|
|
|
|
import com.todoroo.astrid.service.TaskMover
|
|
|
|
|
@ -16,13 +17,11 @@ import com.todoroo.astrid.service.Upgrader.Companion.V6_4
|
|
|
|
|
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
|
|
|
|
|
import kotlinx.serialization.Serializable
|
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
|
import kotlinx.serialization.json.decodeFromJsonElement
|
|
|
|
|
import kotlinx.serialization.json.int
|
|
|
|
|
import kotlinx.serialization.json.jsonObject
|
|
|
|
|
import kotlinx.serialization.json.jsonPrimitive
|
|
|
|
|
import org.tasks.LocalBroadcastManager
|
|
|
|
|
import org.tasks.R
|
|
|
|
|
import org.tasks.caldav.VtodoCache
|
|
|
|
|
import org.tasks.data.GoogleTaskAccount
|
|
|
|
|
import org.tasks.data.GoogleTaskList
|
|
|
|
|
import org.tasks.data.convertPictureUri
|
|
|
|
|
import org.tasks.data.dao.AlarmDao
|
|
|
|
|
import org.tasks.data.dao.CaldavDao
|
|
|
|
|
@ -38,22 +37,27 @@ import org.tasks.data.entity.CaldavAccount
|
|
|
|
|
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
|
|
|
|
|
import org.tasks.data.entity.CaldavCalendar
|
|
|
|
|
import org.tasks.data.entity.CaldavTask
|
|
|
|
|
import org.tasks.data.entity.Filter
|
|
|
|
|
import org.tasks.data.entity.Geofence
|
|
|
|
|
import org.tasks.data.entity.Place
|
|
|
|
|
import org.tasks.data.entity.Tag
|
|
|
|
|
import org.tasks.data.entity.TagData
|
|
|
|
|
import org.tasks.data.entity.Task
|
|
|
|
|
import org.tasks.data.entity.TaskAttachment
|
|
|
|
|
import org.tasks.data.entity.TaskListMetadata
|
|
|
|
|
import org.tasks.db.Migrations.repeatFrom
|
|
|
|
|
import org.tasks.db.Migrations.withoutFrom
|
|
|
|
|
import org.tasks.extensions.forEach
|
|
|
|
|
import org.tasks.extensions.jsonString
|
|
|
|
|
import org.tasks.filters.FilterCriteriaProvider
|
|
|
|
|
import org.tasks.preferences.Preferences
|
|
|
|
|
import timber.log.Timber
|
|
|
|
|
import java.io.FileNotFoundException
|
|
|
|
|
import java.io.IOException
|
|
|
|
|
import java.io.InputStream
|
|
|
|
|
import java.io.InputStreamReader
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
|
|
|
|
class TasksJsonImporter @Inject constructor(
|
|
|
|
|
private val tagDataDao: TagDataDao,
|
|
|
|
|
private val userActivityDao: UserActivityDao,
|
|
|
|
|
@ -71,7 +75,6 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
private val vtodoCache: VtodoCache,
|
|
|
|
|
private val filterCriteriaProvider: FilterCriteriaProvider,
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
private val result = ImportResult()
|
|
|
|
|
|
|
|
|
|
private fun setProgressMessage(
|
|
|
|
|
@ -83,67 +86,65 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun importTasks(context: Context, backupFile: Uri?, progressDialog: ProgressDialog?): ImportResult {
|
|
|
|
|
Timber.d("Importing backup file $backupFile")
|
|
|
|
|
val handler = Handler(context.mainLooper)
|
|
|
|
|
val `is`: InputStream? = try {
|
|
|
|
|
context.contentResolver.openInputStream(backupFile!!)
|
|
|
|
|
} catch (e: FileNotFoundException) {
|
|
|
|
|
throw IllegalStateException(e)
|
|
|
|
|
}
|
|
|
|
|
val reader = InputStreamReader(`is`, TasksJsonExporter.UTF_8)
|
|
|
|
|
val input = Json.parseToJsonElement(reader.readText())
|
|
|
|
|
val bufferedReader = `is`!!.bufferedReader()
|
|
|
|
|
val reader = JsonReader(bufferedReader)
|
|
|
|
|
reader.isLenient = true
|
|
|
|
|
val ignoreKeys = ignorePrefs.map { context.getString(it) }
|
|
|
|
|
try {
|
|
|
|
|
val data = input.jsonObject["data"]!!
|
|
|
|
|
val version = input.jsonObject["version"]!!.jsonPrimitive.int
|
|
|
|
|
val backupContainer = json.decodeFromJsonElement<BackupContainer>(data)
|
|
|
|
|
backupContainer.tags?.forEach { tagData ->
|
|
|
|
|
findTagData(tagData)?.let {
|
|
|
|
|
return@forEach
|
|
|
|
|
}
|
|
|
|
|
tagDataDao.insert(
|
|
|
|
|
tagData.copy(
|
|
|
|
|
color = themeToColor(context, version, tagData.color ?: 0),
|
|
|
|
|
icon = tagData.icon.migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
backupContainer.googleTaskAccounts?.forEach { googleTaskAccount ->
|
|
|
|
|
if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
CaldavAccount(
|
|
|
|
|
accountType = TYPE_GOOGLE_TASKS,
|
|
|
|
|
uuid = googleTaskAccount.account,
|
|
|
|
|
name = googleTaskAccount.account,
|
|
|
|
|
username = googleTaskAccount.account,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
reader.beginObject()
|
|
|
|
|
var version = 0
|
|
|
|
|
while (reader.hasNext()) {
|
|
|
|
|
when (val name = reader.nextName()) {
|
|
|
|
|
"version" -> version = reader.nextInt().also { Timber.d("Backup version: $it") }
|
|
|
|
|
"timestamp" -> reader.nextLong().let { Timber.d("Backup timestamp: $it") }
|
|
|
|
|
"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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.places?.forEach { place ->
|
|
|
|
|
"places" -> reader.forEach<Place> { place ->
|
|
|
|
|
if (locationDao.getByUid(place.uid!!) == null) {
|
|
|
|
|
locationDao.insert(
|
|
|
|
|
place.copy(
|
|
|
|
|
icon = place.icon.migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
place.copy(icon = place.icon.migrateLegacyIcon())
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.googleTaskLists?.forEach { googleTaskList ->
|
|
|
|
|
if (caldavDao.getCalendar(googleTaskList.remoteId!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
CaldavCalendar(
|
|
|
|
|
account = googleTaskList.account,
|
|
|
|
|
uuid = googleTaskList.remoteId,
|
|
|
|
|
color = themeToColor(context, version, googleTaskList.color ?: 0),
|
|
|
|
|
icon = googleTaskList.icon?.toString().migrateLegacyIcon(),
|
|
|
|
|
"tags" -> reader.forEach<TagData> { tagData ->
|
|
|
|
|
findTagData(tagData)?.let {
|
|
|
|
|
return@forEach
|
|
|
|
|
}
|
|
|
|
|
tagDataDao.insert(
|
|
|
|
|
tagData.copy(
|
|
|
|
|
color = themeToColor(context, version, tagData.color ?: 0),
|
|
|
|
|
icon = tagData.icon.migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"filters" -> reader.forEach<Filter> {
|
|
|
|
|
it
|
|
|
|
|
.let {
|
|
|
|
|
if (version < Upgrade_13_2.VERSION)
|
|
|
|
|
filterCriteriaProvider.rebuildFilter(it)
|
|
|
|
|
else
|
|
|
|
|
it
|
|
|
|
|
}
|
|
|
|
|
backupContainer.filters
|
|
|
|
|
?.map {
|
|
|
|
|
if (version < Upgrade_13_2.VERSION) filterCriteriaProvider.rebuildFilter(it)
|
|
|
|
|
else it
|
|
|
|
|
}?.forEach { filter ->
|
|
|
|
|
.let { filter ->
|
|
|
|
|
if (filterDao.getByName(filter.title!!) == null) {
|
|
|
|
|
filterDao.insert(
|
|
|
|
|
filter.copy(
|
|
|
|
|
@ -153,12 +154,13 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.caldavAccounts?.forEach { account ->
|
|
|
|
|
}
|
|
|
|
|
"caldavAccounts" -> reader.forEach<CaldavAccount> { account ->
|
|
|
|
|
if (caldavDao.getAccountByUuid(account.uuid!!) == null) {
|
|
|
|
|
caldavDao.insert(account)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.caldavCalendars?.forEach { calendar ->
|
|
|
|
|
"caldavCalendars" -> reader.forEach<CaldavCalendar> { calendar ->
|
|
|
|
|
if (caldavDao.getCalendarByUuid(calendar.uuid!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
calendar.copy(
|
|
|
|
|
@ -168,28 +170,102 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.taskListMetadata?.forEach { tlm ->
|
|
|
|
|
"taskListMetadata" -> reader.forEach<TaskListMetadata> { tlm ->
|
|
|
|
|
val id = tlm.filter.takeIf { it?.isNotBlank() == true } ?: tlm.tagUuid!!
|
|
|
|
|
if (taskListMetadataDao.fetchByTagOrFilter(id) == null) {
|
|
|
|
|
taskListMetadataDao.insert(tlm)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.taskAttachments?.forEach { attachment ->
|
|
|
|
|
"taskAttachments" -> reader.forEach<TaskAttachment> { attachment ->
|
|
|
|
|
if (taskAttachmentDao.getAttachment(attachment.remoteId) == null) {
|
|
|
|
|
taskAttachmentDao.insert(attachment)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
backupContainer.tasks?.forEach { backup ->
|
|
|
|
|
result.taskCount++
|
|
|
|
|
setProgressMessage(
|
|
|
|
|
handler,
|
|
|
|
|
progressDialog,
|
|
|
|
|
context.getString(R.string.import_progress_read, result.taskCount))
|
|
|
|
|
"intPrefs" ->
|
|
|
|
|
Json.decodeFromString<Map<String, Integer>>(reader.jsonString())
|
|
|
|
|
.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
.forEach { (k, v) -> preferences.setInt(k, v as Int) }
|
|
|
|
|
"longPrefs" ->
|
|
|
|
|
Json.decodeFromString<Map<String, java.lang.Long>>(reader.jsonString())
|
|
|
|
|
.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
.forEach { (k, v) -> preferences.setLong(k, v as Long)}
|
|
|
|
|
"stringPrefs" ->
|
|
|
|
|
Json.decodeFromString<Map<String, String>>(reader.jsonString())
|
|
|
|
|
.filterNot { (k, _) -> ignoreKeys.contains(k) }
|
|
|
|
|
.forEach { (k, v) -> preferences.setString(k, v)}
|
|
|
|
|
"boolPrefs" ->
|
|
|
|
|
Json.decodeFromString<Map<String, java.lang.Boolean>>(reader.jsonString())
|
|
|
|
|
.filterNot { (k, _) -> ignoreKeys.contains(k) }
|
|
|
|
|
.forEach { (k, v) -> preferences.setBoolean(k, v as Boolean) }
|
|
|
|
|
"setPrefs" ->
|
|
|
|
|
Json.decodeFromString<Map<String, Set<String>>>(reader.jsonString())
|
|
|
|
|
.filterNot { (k, _) -> ignoreKeys.contains(k) }
|
|
|
|
|
.forEach { (k, v) -> preferences.setStringSet(k, v as HashSet<String>)}
|
|
|
|
|
"googleTaskAccounts" -> reader.forEach<GoogleTaskAccount> { googleTaskAccount ->
|
|
|
|
|
if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
CaldavAccount(
|
|
|
|
|
accountType = TYPE_GOOGLE_TASKS,
|
|
|
|
|
uuid = googleTaskAccount.account,
|
|
|
|
|
name = googleTaskAccount.account,
|
|
|
|
|
username = googleTaskAccount.account,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"googleTaskLists" -> reader.forEach<GoogleTaskList> { googleTaskList ->
|
|
|
|
|
if (caldavDao.getCalendar(googleTaskList.remoteId!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
CaldavCalendar(
|
|
|
|
|
account = googleTaskList.account,
|
|
|
|
|
uuid = googleTaskList.remoteId,
|
|
|
|
|
color = themeToColor(context, version, googleTaskList.color ?: 0),
|
|
|
|
|
icon = googleTaskList.icon?.toString().migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
Timber.w("Skipping $element")
|
|
|
|
|
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)
|
|
|
|
|
preferences.setInt(
|
|
|
|
|
R.string.p_theme_color,
|
|
|
|
|
getAndroidColor(context, themeIndex))
|
|
|
|
|
}
|
|
|
|
|
if (version < Upgrader.V9_6) {
|
|
|
|
|
taskMover.migrateLocalTasks()
|
|
|
|
|
}
|
|
|
|
|
Timber.d("Updating parents")
|
|
|
|
|
caldavDao.updateParents()
|
|
|
|
|
reader.close()
|
|
|
|
|
bufferedReader.close()
|
|
|
|
|
`is`!!.close()
|
|
|
|
|
} catch (e: IOException) {
|
|
|
|
|
Timber.e(e)
|
|
|
|
|
}
|
|
|
|
|
localBroadcastManager.broadcastRefresh()
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun importTask(backup: TaskBackup, version: Int) {
|
|
|
|
|
val task = backup.task
|
|
|
|
|
taskDao.fetch(task.uuid)
|
|
|
|
|
?.let {
|
|
|
|
|
result.skipCount++
|
|
|
|
|
return@forEach
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
backup.caldavTasks
|
|
|
|
|
@ -209,7 +285,7 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
} == true
|
|
|
|
|
) {
|
|
|
|
|
result.skipCount++
|
|
|
|
|
return@forEach
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
task.suppressRefresh()
|
|
|
|
|
task.suppressSync()
|
|
|
|
|
@ -305,46 +381,6 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
}
|
|
|
|
|
result.importCount++
|
|
|
|
|
}
|
|
|
|
|
Timber.d("Updating parents")
|
|
|
|
|
caldavDao.updateParents()
|
|
|
|
|
val ignoreKeys = ignorePrefs.map { context.getString(it) }
|
|
|
|
|
backupContainer
|
|
|
|
|
.intPrefs
|
|
|
|
|
?.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
?.forEach { (key, value) -> preferences.setInt(key, value as Int) }
|
|
|
|
|
backupContainer
|
|
|
|
|
.longPrefs
|
|
|
|
|
?.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
?.forEach { (key, value) -> preferences.setLong(key, value as Long) }
|
|
|
|
|
backupContainer
|
|
|
|
|
.stringPrefs
|
|
|
|
|
?.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
?.forEach { (key, value) -> preferences.setString(key, value) }
|
|
|
|
|
backupContainer
|
|
|
|
|
.boolPrefs
|
|
|
|
|
?.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
?.forEach { (key, value) -> preferences.setBoolean(key, value as Boolean) }
|
|
|
|
|
backupContainer
|
|
|
|
|
.setPrefs
|
|
|
|
|
?.filterNot { (key, _) -> ignoreKeys.contains(key) }
|
|
|
|
|
?.forEach { (key, value) -> preferences.setStringSet(key, value as HashSet<String>)}
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
reader.close()
|
|
|
|
|
`is`!!.close()
|
|
|
|
|
} catch (e: IOException) {
|
|
|
|
|
Timber.e(e)
|
|
|
|
|
}
|
|
|
|
|
localBroadcastManager.broadcastRefresh()
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun findTagData(tagData: TagData) =
|
|
|
|
|
findTagData(tagData.remoteId!!, tagData.name!!)
|
|
|
|
|
|