You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/backup/TasksJsonImporter.kt

325 lines
14 KiB
Kotlin

package org.tasks.backup
import android.app.ProgressDialog
import android.content.Context
import android.net.Uri
import android.os.Handler
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.service.Upgrader
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.V6_4
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.caldav.VtodoCache
import org.tasks.data.*
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.Place.Companion.newPlace
import org.tasks.db.Migrations.repeatFrom
import org.tasks.db.Migrations.withoutFrom
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
class TasksJsonImporter @Inject constructor(
private val tagDataDao: TagDataDao,
private val userActivityDao: UserActivityDao,
private val taskDao: TaskDao,
private val locationDao: LocationDao,
private val localBroadcastManager: LocalBroadcastManager,
private val alarmDao: AlarmDao,
private val tagDao: TagDao,
private val filterDao: FilterDao,
private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao,
private val preferences: Preferences,
private val taskMover: TaskMover,
private val taskListMetadataDao: TaskListMetadataDao,
private val vtodoCache: VtodoCache,
) {
private val result = ImportResult()
private fun setProgressMessage(
handler: Handler, progressDialog: ProgressDialog?, message: String) {
if (progressDialog == null) {
return
}
handler.post { progressDialog.setMessage(message) }
}
suspend fun importTasks(context: Context, backupFile: Uri?, progressDialog: ProgressDialog?): ImportResult {
val handler = Handler(context.mainLooper)
val gson = Gson()
val `is`: InputStream? = try {
context.contentResolver.openInputStream(backupFile!!)
} catch (e: FileNotFoundException) {
throw IllegalStateException(e)
}
val reader = InputStreamReader(`is`, TasksJsonExporter.UTF_8)
val input = gson.fromJson(reader, JsonObject::class.java)
try {
val data = input["data"]
val version = input["version"].asInt
val backupContainer = gson.fromJson(data, BackupContainer::class.java)
backupContainer.tags?.forEach { tagData ->
findTagData(tagData)?.let {
return@forEach
}
tagData.setColor(themeToColor(context, version, tagData.getColor()!!))
tagDataDao.createNew(tagData)
}
backupContainer.googleTaskAccounts?.forEach { googleTaskAccount ->
if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) {
caldavDao.insert(
CaldavAccount().apply {
accountType = TYPE_GOOGLE_TASKS
uuid = googleTaskAccount.account
name = googleTaskAccount.account
username = googleTaskAccount.account
}
)
}
}
backupContainer.places?.forEach { place ->
if (locationDao.getByUid(place.uid!!) == null) {
locationDao.insert(place)
}
}
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),
)
)
}
}
backupContainer.filters?.forEach { filter ->
filter.setColor(themeToColor(context, version, filter.getColor()!!))
if (filterDao.getByName(filter.title!!) == null) {
filterDao.insert(filter)
}
}
backupContainer.caldavAccounts?.forEach { account ->
if (caldavDao.getAccountByUuid(account.uuid!!) == null) {
caldavDao.insert(account)
}
}
backupContainer.caldavCalendars?.forEach { calendar ->
if (caldavDao.getCalendarByUuid(calendar.uuid!!) == null) {
caldavDao.insert(
calendar.copy(color = themeToColor(context, version, calendar.color))
)
}
}
backupContainer.taskListMetadata?.forEach { tlm ->
val id = tlm.filter.takeIf { it?.isNotBlank() == true } ?: tlm.tagUuid!!
if (taskListMetadataDao.fetchByTagOrFilter(id) == null) {
taskListMetadataDao.insert(tlm)
}
}
backupContainer.taskAttachments?.forEach { 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))
val task = backup.task
taskDao.fetch(task.uuid)
?.let {
result.skipCount++
return@forEach
}
if (true == backup.caldavTasks
?.filter { it.deleted == 0L }
?.any { caldavDao.getTask(it.calendar!!, it.`object`!!) != null }) {
result.skipCount++
return@forEach
}
task.suppressRefresh()
task.suppressSync()
taskDao.createNew(task)
val taskId = task.id
val taskUuid = task.uuid
for (alarm in backup.alarms) {
alarm.task = taskId
alarmDao.insert(alarm)
}
if (version < V12_4) {
task.defaultReminders(task.ringFlags)
alarmDao.insert(task.getDefaultAlarms())
task.ringFlags = when {
task.isNotifyModeFive -> Task.NOTIFY_MODE_FIVE
task.isNotifyModeNonstop -> Task.NOTIFY_MODE_NONSTOP
else -> 0
}
taskDao.save(task)
}
if (version < V12_8) {
task.repeatFrom = task.recurrence.repeatFrom()
task.recurrence = task.recurrence.withoutFrom()
}
for (comment in backup.comments) {
comment.targetId = taskUuid
if (version < V6_4) {
comment.convertPictureUri()
}
userActivityDao.createNew(comment)
}
for (googleTask in backup.google) {
caldavDao.insert(
CaldavTask(
task = taskId,
calendar = googleTask.listId,
remoteId = googleTask.remoteId,
).apply {
remoteOrder = googleTask.remoteOrder
remoteParent = googleTask.remoteParent
lastSync = googleTask.lastSync
}
)
}
for (location in backup.locations) {
val place = newPlace()
place.longitude = location.longitude
place.latitude = location.latitude
place.name = location.name
place.address = location.address
place.url = location.url
place.phone = location.phone
locationDao.insert(place)
val geofence = Geofence()
geofence.task = taskId
geofence.place = place.uid
geofence.isArrival = location.arrival
geofence.isDeparture = location.departure
locationDao.insert(geofence)
}
for (tag in backup.tags) {
val tagData = findTagData(tag) ?: continue
tag.task = taskId
tag.tagUid = tagData.remoteId
tag.setTaskUid(taskUuid)
tagDao.insert(tag)
}
backup.geofences?.forEach { geofence ->
geofence.task = taskId
locationDao.insert(geofence)
}
backup.attachments
?.mapNotNull { taskAttachmentDao.getAttachment(it.attachmentUid) }
?.map {
Attachment(
task = taskId,
fileId = it.id!!,
attachmentUid = it.remoteId,
)
}
?.let { taskAttachmentDao.insert(it) }
backup.caldavTasks?.forEach { caldavTask ->
caldavTask.task = taskId
caldavDao.insert(caldavTask)
}
backup.vtodo?.let {
val caldavTask =
backup.caldavTasks?.firstOrNull { t -> !t.isDeleted() } ?: return@let
val caldavCalendar = caldavDao.getCalendar(caldavTask.calendar!!) ?: return@let
vtodoCache.putVtodo(caldavCalendar, caldavTask, it)
}
result.importCount++
}
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!!)
private suspend fun findTagData(tag: Tag) = findTagData(tag.tagUid!!, tag.name!!)
private suspend fun findTagData(uid: String, name: String): TagData? =
tagDataDao.getByUuid(uid) ?: tagDataDao.getTagByName(name)
private fun themeToColor(context: Context, version: Int, color: Int) =
if (version < Upgrader.V8_2) getAndroidColor(context, color) else color
class ImportResult {
var taskCount = 0
var importCount = 0
var skipCount = 0
}
class LegacyLocation {
var name: String? = null
var address: String? = null
var phone: String? = null
var url: String? = null
var latitude = 0.0
var longitude = 0.0
var radius = 0
var arrival = false
var departure = false
}
companion object {
private val ignorePrefs = intArrayOf(
R.string.p_current_version,
R.string.p_backups_android_backup_last
)
}
}