|
|
|
|
@ -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.V6_4
|
|
|
|
|
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import kotlinx.serialization.Serializable
|
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
|
import org.tasks.LocalBroadcastManager
|
|
|
|
|
@ -85,9 +87,37 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
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")
|
|
|
|
|
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 {
|
|
|
|
|
context.contentResolver.openInputStream(backupFile!!)
|
|
|
|
|
} catch (e: FileNotFoundException) {
|
|
|
|
|
@ -97,167 +127,192 @@ class TasksJsonImporter @Inject constructor(
|
|
|
|
|
val reader = JsonReader(bufferedReader)
|
|
|
|
|
reader.isLenient = true
|
|
|
|
|
val ignoreKeys = ignorePrefs.map { context.getString(it) }
|
|
|
|
|
try {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
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()) {
|
|
|
|
|
"places" -> reader.forEach<Place> { place ->
|
|
|
|
|
if (locationDao.getByUid(place.uid!!) == null) {
|
|
|
|
|
locationDao.insert(
|
|
|
|
|
place.copy(icon = place.icon.migrateLegacyIcon())
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"places" -> reader.forEach<Place> { place ->
|
|
|
|
|
if (locationDao.getByUid(place.uid!!) == null) {
|
|
|
|
|
locationDao.insert(
|
|
|
|
|
place.copy(icon = place.icon.migrateLegacyIcon())
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"tags" -> reader.forEach<TagData> { tagData ->
|
|
|
|
|
findTagData(tagData)?.let {
|
|
|
|
|
return@forEach
|
|
|
|
|
}
|
|
|
|
|
"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(),
|
|
|
|
|
)
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
.let { filter ->
|
|
|
|
|
if (filterDao.getByName(filter.title!!) == null) {
|
|
|
|
|
filterDao.insert(
|
|
|
|
|
filter.copy(
|
|
|
|
|
color = themeToColor(context, version, filter.color ?: 0),
|
|
|
|
|
icon = filter.icon.migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"filters" -> reader.forEach<Filter> {
|
|
|
|
|
it
|
|
|
|
|
.let {
|
|
|
|
|
if (version < Upgrade_13_2.VERSION)
|
|
|
|
|
filterCriteriaProvider.rebuildFilter(it)
|
|
|
|
|
else
|
|
|
|
|
it
|
|
|
|
|
}
|
|
|
|
|
.let { filter ->
|
|
|
|
|
if (filterDao.getByName(filter.title!!) == null) {
|
|
|
|
|
filterDao.insert(
|
|
|
|
|
filter.copy(
|
|
|
|
|
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) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
calendar.copy(
|
|
|
|
|
color = themeToColor(context, version, calendar.color),
|
|
|
|
|
icon = calendar.icon.migrateLegacyIcon(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"caldavCalendars" -> reader.forEach<CaldavCalendar> { calendar ->
|
|
|
|
|
if (caldavDao.getCalendarByUuid(calendar.uuid!!) == null) {
|
|
|
|
|
caldavDao.insert(
|
|
|
|
|
calendar.copy(
|
|
|
|
|
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!!
|
|
|
|
|
if (taskListMetadataDao.fetchByTagOrFilter(id) == null) {
|
|
|
|
|
taskListMetadataDao.insert(tlm)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"taskListMetadata" -> reader.forEach<TaskListMetadata> { tlm ->
|
|
|
|
|
val id = tlm.filter.takeIf { it?.isNotBlank() == true } ?: tlm.tagUuid!!
|
|
|
|
|
if (taskListMetadataDao.fetchByTagOrFilter(id) == null) {
|
|
|
|
|
taskListMetadataDao.insert(tlm)
|
|
|
|
|
}
|
|
|
|
|
"taskAttachments" -> reader.forEach<TaskAttachment> { attachment ->
|
|
|
|
|
if (taskAttachmentDao.getAttachment(attachment.remoteId) == null) {
|
|
|
|
|
taskAttachmentDao.insert(attachment)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"taskAttachments" -> reader.forEach<TaskAttachment> { attachment ->
|
|
|
|
|
if (taskAttachmentDao.getAttachment(attachment.remoteId) == null) {
|
|
|
|
|
taskAttachmentDao.insert(attachment)
|
|
|
|
|
}
|
|
|
|
|
"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,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"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(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"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()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
preferences.setInt(
|
|
|
|
|
R.string.p_theme_color,
|
|
|
|
|
getAndroidColor(context, themeIndex))
|
|
|
|
|
}
|
|
|
|
|
if (version < Upgrader.V9_6) {
|
|
|
|
|
taskMover.migrateLocalTasks()
|
|
|
|
|
}
|
|
|
|
|
reader.close()
|
|
|
|
|
bufferedReader.close()
|
|
|
|
|
`is`.close()
|
|
|
|
|
return version
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
return result
|
|
|
|
|
reader.close()
|
|
|
|
|
bufferedReader.close()
|
|
|
|
|
`is`.close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun importTask(backup: TaskBackup, version: Int) {
|
|
|
|
|
|