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)} 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 ) } }