Stream data to backup files

pull/3425/head
Alex Baker 9 months ago
parent 1e3b863dd8
commit 4c4d5cdc14

@ -8,14 +8,12 @@ import android.net.Uri
import android.os.Handler import android.os.Handler
import com.google.common.io.Files import com.google.common.io.Files
import com.todoroo.andlib.utility.DialogUtilities import com.todoroo.andlib.utility.DialogUtilities
import kotlinx.serialization.encodeToString import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.encodeToJsonElement
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.backup.BackupContainer.TaskBackup
import org.tasks.caldav.VtodoCache import org.tasks.caldav.VtodoCache
import org.tasks.data.* import org.tasks.data.*
import org.tasks.data.dao.AlarmDao import org.tasks.data.dao.AlarmDao
@ -28,7 +26,6 @@ import org.tasks.data.dao.TaskAttachmentDao
import org.tasks.data.dao.TaskDao import org.tasks.data.dao.TaskDao
import org.tasks.data.dao.TaskListMetadataDao import org.tasks.data.dao.TaskListMetadataDao
import org.tasks.data.dao.UserActivityDao import org.tasks.data.dao.UserActivityDao
import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
@ -39,8 +36,9 @@ import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.io.OutputStreamWriter import java.io.Writer
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.Set
import javax.inject.Inject import javax.inject.Inject
class TasksJsonExporter @Inject constructor( class TasksJsonExporter @Inject constructor(
@ -57,8 +55,7 @@ class TasksJsonExporter @Inject constructor(
private val workManager: WorkManager, private val workManager: WorkManager,
private val taskListMetadataDao: TaskListMetadataDao, private val taskListMetadataDao: TaskListMetadataDao,
private val vtodoCache: VtodoCache, private val vtodoCache: VtodoCache,
) { ) {
private var context: Context? = null private var context: Context? = null
private var exportCount = 0 private var exportCount = 0
private var progressDialog: ProgressDialog? = null private var progressDialog: ProgressDialog? = null
@ -84,7 +81,7 @@ class TasksJsonExporter @Inject constructor(
private suspend fun runBackup(exportType: ExportType) { private suspend fun runBackup(exportType: ExportType) {
try { try {
val filename = getFileName(exportType) val filename = getFileName(exportType)
val tasks = taskDao.getAll() val tasks = taskDao.getAllTaskIds()
val file = File(String.format("%s/%s", context!!.filesDir, BackupConstants.INTERNAL_BACKUP)) val file = File(String.format("%s/%s", context!!.filesDir, BackupConstants.INTERNAL_BACKUP))
file.delete() file.delete()
file.createNewFile() file.createNewFile()
@ -117,53 +114,50 @@ class TasksJsonExporter @Inject constructor(
} }
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun doTasksExport(os: OutputStream?, tasks: List<Task>) { private suspend fun doTasksExport(os: OutputStream?, taskIds: List<Long>) = withContext(Dispatchers.IO) {
val taskBackups: MutableList<TaskBackup> = ArrayList() val writer = os!!.bufferedWriter()
for (task in tasks) { with (JsonWriter(writer)) {
setProgress(taskBackups.size, tasks.size) write("{")
val taskId = task.id write("version", BuildConfig.VERSION_CODE)
val caldavTasks = caldavDao.getTasks(taskId) write("timestamp", currentTimeMillis())
taskBackups.add( write("\"data\":{")
TaskBackup( write("\"tasks\":[")
task = task, taskIds.forEachIndexed { index, id ->
alarms = alarmDao.getAlarms(taskId), setProgress(index, taskIds.size)
geofences = locationDao.getGeofencesForTask(taskId), write("{")
tags = tagDao.getTagsForTask(taskId), write("task", taskDao.fetch(id)!!)
comments = userActivityDao.getComments(taskId), write("alarms", alarmDao.getAlarms(id))
attachments = taskAttachmentDao.getAttachmentsForTask(taskId), write("geofences", locationDao.getGeofencesForTask(id))
caldavTasks = caldavTasks, write("tags", tagDao.getTagsForTask(id))
vtodo = vtodoCache.getVtodo(caldavTasks.firstOrNull { !it.isDeleted() }) write("comments", userActivityDao.getComments(id))
) write("attachments", taskAttachmentDao.getAttachmentsForTask(id))
) val caldavTasks = caldavDao.getTasks(id)
vtodoCache
.getVtodo(caldavTasks.firstOrNull { !it.isDeleted() })
?.let { write("vtodo", it) }
write("caldavTasks", caldavTasks, lastItem = true)
write("}")
if (index < taskIds.size - 1) write(",")
}
write("],")
write("places", locationDao.getPlaces())
write("tags", tagDataDao.getAll())
write("filters", filterDao.getFilters())
write("caldavAccounts", caldavDao.getAccounts())
write("caldavCalendars", caldavDao.getCalendars())
write("taskListMetadata", taskListMetadataDao.getAll())
write("taskAttachments", taskAttachmentDao.getAttachments())
write("intPrefs", preferences.getPrefs(Integer::class.java))
write("longPrefs", preferences.getPrefs(java.lang.Long::class.java))
write("stringPrefs", preferences.getPrefs(String::class.java))
write("boolPrefs", preferences.getPrefs(java.lang.Boolean::class.java))
write("setPrefs", preferences.getPrefs(Set::class.java) as Map<String, Set<String>>, lastItem = true)
write("}")
write("}")
} }
val data = JsonObject( writer.close()
mapOf( os.close()
"version" to JsonPrimitive(BuildConfig.VERSION_CODE), exportCount = taskIds.size
"timestamp" to JsonPrimitive(currentTimeMillis()),
"data" to Json.encodeToJsonElement(
BackupContainer(
taskBackups,
locationDao.getPlaces(),
tagDataDao.getAll(),
filterDao.getFilters(),
caldavDao.getAccounts(),
caldavDao.getCalendars(),
taskListMetadataDao.getAll(),
taskAttachmentDao.getAttachments(),
preferences.getPrefs(Integer::class.java),
preferences.getPrefs(java.lang.Long::class.java),
preferences.getPrefs(String::class.java),
preferences.getPrefs(java.lang.Boolean::class.java),
preferences.getPrefs(java.util.Set::class.java) as Map<String, java.util.Set<String>>,
)
)
)
)
val out = OutputStreamWriter(os, UTF_8)
val json = if (BuildConfig.DEBUG) Json { prettyPrint = true } else Json
out.write(json.encodeToString(data))
out.close()
exportCount = taskBackups.size
} }
private fun onFinishExport(outputFile: String) = post { private fun onFinishExport(outputFile: String) = post {
@ -193,5 +187,12 @@ class TasksJsonExporter @Inject constructor(
private const val EXTENSION = ".json" private const val EXTENSION = ".json"
private val dateForExport: String private val dateForExport: String
get() = newDateTime().toString("yyyyMMdd'T'HHmm") get() = newDateTime().toString("yyyyMMdd'T'HHmm")
class JsonWriter(val writer: Writer, val json: Json = Json) {
fun write(data: String) = writer.write(data)
inline fun <reified T> write(key: String, value: @Serializable T, lastItem: Boolean = false) where T : Any =
writer.write("\"$key\":${json.encodeToString(value)}${if (lastItem) "" else ","}")
}
} }
} }

@ -94,6 +94,9 @@ FROM (
@Query("SELECT * FROM tasks") @Query("SELECT * FROM tasks")
abstract suspend fun getAll(): List<Task> abstract suspend fun getAll(): List<Task>
@Query("SELECT _id FROM tasks")
abstract suspend fun getAllTaskIds(): List<Long>
@Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''") @Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun getAllCalendarEvents(): List<String> abstract suspend fun getAllCalendarEvents(): List<String>

Loading…
Cancel
Save