mirror of https://github.com/tasks/tasks
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.
391 lines
15 KiB
Kotlin
391 lines
15 KiB
Kotlin
package com.todoroo.astrid.service
|
|
|
|
import android.content.Context
|
|
import androidx.annotation.ColorRes
|
|
import com.google.common.collect.ImmutableListMultimap
|
|
import com.google.common.collect.ListMultimap
|
|
import com.google.common.collect.Multimaps
|
|
import com.todoroo.astrid.api.GtasksFilter
|
|
import com.todoroo.astrid.dao.TaskDao
|
|
import dagger.Lazy
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import kotlinx.coroutines.runBlocking
|
|
import org.tasks.R
|
|
import org.tasks.Strings.isNullOrEmpty
|
|
import org.tasks.caldav.iCalendar
|
|
import org.tasks.caldav.iCalendar.Companion.fromVtodo
|
|
import org.tasks.caldav.iCalendar.Companion.order
|
|
import org.tasks.caldav.iCalendar.Companion.parent
|
|
import org.tasks.data.CaldavDao
|
|
import org.tasks.data.CaldavTask
|
|
import org.tasks.data.CaldavTaskContainer
|
|
import org.tasks.data.FilterDao
|
|
import org.tasks.data.GoogleTaskAccount
|
|
import org.tasks.data.GoogleTaskDao
|
|
import org.tasks.data.GoogleTaskListDao
|
|
import org.tasks.data.Location
|
|
import org.tasks.data.LocationDao
|
|
import org.tasks.data.Tag
|
|
import org.tasks.data.TagDao
|
|
import org.tasks.data.TagData
|
|
import org.tasks.data.TagDataDao
|
|
import org.tasks.data.TaskAttachmentDao
|
|
import org.tasks.data.UpgraderDao
|
|
import org.tasks.data.UserActivityDao
|
|
import org.tasks.preferences.DefaultFilterProvider
|
|
import org.tasks.preferences.Preferences
|
|
import org.tasks.widget.AppWidgetManager
|
|
import org.tasks.widget.WidgetPreferences
|
|
import java.io.File
|
|
import javax.inject.Inject
|
|
|
|
class Upgrader @Inject constructor(
|
|
@param:ApplicationContext private val context: Context,
|
|
private val preferences: Preferences,
|
|
private val tagDataDao: TagDataDao,
|
|
private val tagDao: TagDao,
|
|
private val filterDao: FilterDao,
|
|
private val defaultFilterProvider: DefaultFilterProvider,
|
|
private val googleTaskListDao: GoogleTaskListDao,
|
|
private val googleTaskDao: GoogleTaskDao,
|
|
private val userActivityDao: UserActivityDao,
|
|
private val taskAttachmentDao: TaskAttachmentDao,
|
|
private val caldavDao: CaldavDao,
|
|
private val taskDao: TaskDao,
|
|
private val locationDao: LocationDao,
|
|
private val iCal: iCalendar,
|
|
private val widgetManager: AppWidgetManager,
|
|
private val taskMover: TaskMover,
|
|
private val upgraderDao: UpgraderDao,
|
|
private val upgrade_11_3: Lazy<Upgrade_11_3>,
|
|
private val upgrade_11_12_3: Lazy<Upgrade_11_12_3>,
|
|
) {
|
|
|
|
fun upgrade(from: Int, to: Int) {
|
|
if (from > 0) {
|
|
run(from, V4_9_5) { removeDuplicateTags() }
|
|
run(from, V5_3_0) { migrateFilters() }
|
|
run(from, V6_0_beta_1) { migrateDefaultSyncList() }
|
|
run(from, V6_0_beta_2) { migrateGoogleTaskAccount() }
|
|
run(from, V6_4) { migrateUris() }
|
|
run(from, V6_7) { this.migrateGoogleTaskFilters() }
|
|
run(from, V6_8_1) { this.migrateCaldavFilters() }
|
|
run(from, V6_9) { applyCaldavCategories() }
|
|
run(from, V7_0) { applyCaldavSubtasks() }
|
|
run(from, V8_2) { migrateColors() }
|
|
run(from, V8_5) { applyCaldavGeo() }
|
|
run(from, V8_8) { preferences.setBoolean(R.string.p_linkify_task_edit, true) }
|
|
run(from, V8_10) { migrateWidgets() }
|
|
run(from, V9_3) { applyCaldavOrder() }
|
|
run(from, V9_6) {
|
|
preferences.setBoolean(R.string.p_astrid_sort_enabled, true)
|
|
taskMover.migrateLocalTasks()
|
|
}
|
|
run(from, V9_7) { googleTaskListDao.resetOrders() }
|
|
run(from, V9_7_3) { googleTaskDao.updateParents() }
|
|
run(from, V10_0_2) {
|
|
filterDao.getFilters()
|
|
.filter { it.getSql().trim() == "WHERE" }
|
|
.forEach { filterDao.delete(it) }
|
|
}
|
|
run(from, Upgrade_11_3.VERSION) {
|
|
with(upgrade_11_3.get()) {
|
|
applyiCalendarStartDates()
|
|
applyOpenTaskStartDates()
|
|
}
|
|
}
|
|
run(from, Upgrade_11_12_3.VERSION) {
|
|
with(upgrade_11_12_3.get()) {
|
|
migrateDefaultReminderPreference()
|
|
}
|
|
}
|
|
run(from, V11_13) {
|
|
preferences.setString(R.string.p_completion_ringtone, "")
|
|
}
|
|
preferences.setBoolean(R.string.p_just_updated, true)
|
|
}
|
|
preferences.setCurrentVersion(to)
|
|
}
|
|
|
|
private fun run(from: Int, version: Int, runnable: suspend () -> Unit) {
|
|
if (from < version) {
|
|
runBlocking {
|
|
runnable()
|
|
}
|
|
preferences.setCurrentVersion(version)
|
|
}
|
|
}
|
|
|
|
private fun migrateWidgets() {
|
|
for (widgetId in widgetManager.widgetIds) {
|
|
val widgetPreferences = WidgetPreferences(context, preferences, widgetId)
|
|
widgetPreferences.maintainExistingConfiguration()
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateColors() {
|
|
preferences.setInt(
|
|
R.string.p_theme_color, getAndroidColor(preferences.getInt(R.string.p_theme_color, 7)))
|
|
for (calendar in caldavDao.getCalendars()) {
|
|
calendar.color = getAndroidColor(calendar.color)
|
|
caldavDao.update(calendar)
|
|
}
|
|
for (list in googleTaskListDao.getAllLists()) {
|
|
list.setColor(getAndroidColor(list.getColor()!!))
|
|
googleTaskListDao.update(list)
|
|
}
|
|
for (tagData in tagDataDao.getAll()) {
|
|
tagData.setColor(getAndroidColor(tagData.getColor()!!))
|
|
tagDataDao.update(tagData)
|
|
}
|
|
for (filter in filterDao.getFilters()) {
|
|
filter.setColor(getAndroidColor(filter.getColor()!!))
|
|
filterDao.update(filter)
|
|
}
|
|
}
|
|
|
|
private fun getAndroidColor(index: Int): Int {
|
|
return getAndroidColor(context, index)
|
|
}
|
|
|
|
|
|
private suspend fun applyCaldavOrder() {
|
|
for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) {
|
|
val remoteTask = fromVtodo(task.vtodo!!) ?: continue
|
|
val order: Long? = remoteTask.order
|
|
if (order != null) {
|
|
task.order = order
|
|
caldavDao.update(task)
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun applyCaldavGeo() {
|
|
val tasksWithLocations = locationDao.getActiveGeofences().map(Location::task)
|
|
for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) {
|
|
val taskId = task.task
|
|
if (tasksWithLocations.contains(taskId)) {
|
|
continue
|
|
}
|
|
val remoteTask = fromVtodo(task.vtodo!!) ?: continue
|
|
val geo = remoteTask.geoPosition ?: continue
|
|
iCal.setPlace(taskId, geo)
|
|
}
|
|
taskDao.touch(tasksWithLocations)
|
|
}
|
|
|
|
private suspend fun applyCaldavSubtasks() {
|
|
val updated: MutableList<CaldavTask> = ArrayList()
|
|
for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) {
|
|
val remoteTask = fromVtodo(task.vtodo!!) ?: continue
|
|
task.remoteParent = remoteTask.parent
|
|
if (!isNullOrEmpty(task.remoteParent)) {
|
|
updated.add(task)
|
|
}
|
|
}
|
|
caldavDao.update(updated)
|
|
caldavDao.updateParents()
|
|
}
|
|
|
|
private suspend fun applyCaldavCategories() {
|
|
val tasksWithTags: List<Long> = upgraderDao.tasksWithTags()
|
|
for (container in upgraderDao.tasksWithVtodos()) {
|
|
val remoteTask = fromVtodo(container.vtodo!!)
|
|
if (remoteTask != null) {
|
|
tagDao.insert(container.task, iCal.getTags(remoteTask.categories))
|
|
}
|
|
}
|
|
taskDao.touch(tasksWithTags)
|
|
}
|
|
|
|
private suspend fun removeDuplicateTags() {
|
|
val tagsByUuid: ListMultimap<String, TagData> = Multimaps.index(tagDataDao.tagDataOrderedByName()) { it!!.remoteId }
|
|
for (uuid in tagsByUuid.keySet()) {
|
|
removeDuplicateTagData(tagsByUuid[uuid])
|
|
removeDuplicateTagMetadata(uuid)
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateGoogleTaskFilters() {
|
|
for (filter in filterDao.getFilters()) {
|
|
filter.setSql(migrateGoogleTaskFilters(filter.getSql()))
|
|
filter.criterion = migrateGoogleTaskFilters(filter.criterion)
|
|
filterDao.update(filter)
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateCaldavFilters() {
|
|
for (filter in filterDao.getFilters()) {
|
|
filter.setSql(migrateCaldavFilters(filter.getSql()))
|
|
filter.criterion = migrateCaldavFilters(filter.criterion)
|
|
filterDao.update(filter)
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateFilters() {
|
|
for (filter in filterDao.getFilters()) {
|
|
filter.setSql(migrateMetadata(filter.getSql()))
|
|
filter.criterion = migrateMetadata(filter.criterion)
|
|
filterDao.update(filter)
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateDefaultSyncList() {
|
|
val account = preferences.getStringValue("gtasks_user")
|
|
if (isNullOrEmpty(account)) {
|
|
return
|
|
}
|
|
val defaultGoogleTaskList = preferences.getStringValue("gtasks_defaultlist")
|
|
if (isNullOrEmpty(defaultGoogleTaskList)) {
|
|
// TODO: look up default list
|
|
} else {
|
|
val googleTaskList = googleTaskListDao.getByRemoteId(defaultGoogleTaskList!!)
|
|
if (googleTaskList != null) {
|
|
defaultFilterProvider.defaultList = GtasksFilter(googleTaskList)
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateGoogleTaskAccount() {
|
|
val account = preferences.getStringValue("gtasks_user")
|
|
if (!isNullOrEmpty(account)) {
|
|
val googleTaskAccount = GoogleTaskAccount()
|
|
googleTaskAccount.account = account
|
|
googleTaskListDao.insert(googleTaskAccount)
|
|
for (list in googleTaskListDao.getAllLists()) {
|
|
list.account = account
|
|
googleTaskListDao.insertOrReplace(list)
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun migrateUris() {
|
|
migrateUriPreference(R.string.p_backup_dir)
|
|
migrateUriPreference(R.string.p_attachment_dir)
|
|
for (userActivity in userActivityDao.getComments()) {
|
|
userActivity.convertPictureUri()
|
|
userActivityDao.update(userActivity)
|
|
}
|
|
for (attachment in taskAttachmentDao.getAttachments()) {
|
|
attachment.convertPathUri()
|
|
taskAttachmentDao.update(attachment)
|
|
}
|
|
}
|
|
|
|
private fun migrateUriPreference(pref: Int) {
|
|
val path = preferences.getStringValue(pref)
|
|
if (isNullOrEmpty(path)) {
|
|
return
|
|
}
|
|
val file = File(path)
|
|
try {
|
|
if (file.canWrite()) {
|
|
preferences.setUri(pref, file.toURI())
|
|
} else {
|
|
preferences.remove(pref)
|
|
}
|
|
} catch (ignored: SecurityException) {
|
|
preferences.remove(pref)
|
|
}
|
|
}
|
|
|
|
private fun migrateGoogleTaskFilters(input: String?): String {
|
|
return input.orEmpty()
|
|
.replace("SELECT task FROM google_tasks", "SELECT gt_task as task FROM google_tasks")
|
|
.replace("(list_id", "(gt_list_id")
|
|
.replace("google_tasks.list_id", "google_tasks.gt_list_id")
|
|
.replace("google_tasks.task", "google_tasks.gt_task")
|
|
}
|
|
|
|
private fun migrateCaldavFilters(input: String?): String {
|
|
return input.orEmpty()
|
|
.replace("SELECT task FROM caldav_tasks", "SELECT cd_task as task FROM caldav_tasks")
|
|
.replace("(calendar", "(cd_calendar")
|
|
}
|
|
|
|
private fun migrateMetadata(input: String?): String {
|
|
return input.orEmpty()
|
|
.replace(
|
|
"""SELECT metadata\.task AS task FROM metadata INNER JOIN tasks ON \(\(metadata\.task=tasks\._id\)\) WHERE \(\(\(tasks\.completed=0\) AND \(tasks\.deleted=0\) AND \(tasks\.hideUntil<\(strftime\(\'%s\',\'now\'\)\*1000\)\)\) AND \(metadata\.key=\'tags-tag\'\) AND \(metadata\.value""".toRegex(),
|
|
"SELECT tags.task AS task FROM tags INNER JOIN tasks ON ((tags.task=tasks._id)) WHERE (((tasks.completed=0) AND (tasks.deleted=0) AND (tasks.hideUntil<(strftime('%s','now')*1000))) AND (tags.name")
|
|
.replace(
|
|
"""SELECT metadata\.task AS task FROM metadata INNER JOIN tasks ON \(\(metadata\.task=tasks\._id\)\) WHERE \(\(\(tasks\.completed=0\) AND \(tasks\.deleted=0\) AND \(tasks\.hideUntil<\(strftime\(\'%s\',\'now\'\)\*1000\)\)\) AND \(metadata\.key=\'gtasks\'\) AND \(metadata\.value2""".toRegex(),
|
|
"SELECT google_tasks.task AS task FROM google_tasks INNER JOIN tasks ON ((google_tasks.task=tasks._id)) WHERE (((tasks.completed=0) AND (tasks.deleted=0) AND (tasks.hideUntil<(strftime('%s','now')*1000))) AND (google_tasks.list_id")
|
|
.replace("""AND \(metadata\.deleted=0\)""".toRegex(), "")
|
|
}
|
|
|
|
private suspend fun removeDuplicateTagData(tagData: List<TagData>) {
|
|
if (tagData.size > 1) {
|
|
tagDataDao.delete(tagData.subList(1, tagData.size))
|
|
}
|
|
}
|
|
|
|
private suspend fun removeDuplicateTagMetadata(uuid: String) {
|
|
val metadatas = tagDao.getByTagUid(uuid)
|
|
val metadataByTask: ImmutableListMultimap<Long, Tag> = Multimaps.index(metadatas) { it!!.task }
|
|
for (key in metadataByTask.keySet()) {
|
|
val tags = metadataByTask[key]
|
|
if (tags.size > 1) {
|
|
tagDao.delete(tags.subList(1, tags.size))
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val V4_9_5 = 434
|
|
private const val V5_3_0 = 491
|
|
private const val V6_0_beta_1 = 522
|
|
private const val V6_0_beta_2 = 523
|
|
const val V6_4 = 546
|
|
private const val V6_7 = 585
|
|
private const val V6_8_1 = 607
|
|
private const val V6_9 = 608
|
|
private const val V7_0 = 617
|
|
const val V8_2 = 675
|
|
private const val V8_5 = 700
|
|
private const val V8_8 = 717
|
|
private const val V8_10 = 735
|
|
private const val V9_3 = 90300
|
|
const val V9_6 = 90600
|
|
const val V9_7 = 90700
|
|
const val V9_7_3 = 90704
|
|
const val V10_0_2 = 100012
|
|
const val V11_13 = 111300
|
|
const val V12_3 = 120300
|
|
|
|
@JvmStatic
|
|
fun getAndroidColor(context: Context, index: Int): Int {
|
|
val legacyColor = getLegacyColor(index, 0)
|
|
return if (legacyColor == 0) 0 else context.getColor(legacyColor)
|
|
}
|
|
|
|
@JvmStatic
|
|
@ColorRes
|
|
fun getLegacyColor(index: Int, def: Int): Int {
|
|
return when (index) {
|
|
0 -> R.color.blue_grey_500
|
|
1 -> R.color.grey_900
|
|
2 -> R.color.red_500
|
|
3 -> R.color.pink_500
|
|
4 -> R.color.purple_500
|
|
5 -> R.color.deep_purple_500
|
|
6 -> R.color.indigo_500
|
|
7 -> R.color.blue_500
|
|
8 -> R.color.light_blue_500
|
|
9 -> R.color.cyan_500
|
|
10 -> R.color.teal_500
|
|
11 -> R.color.green_500
|
|
12 -> R.color.light_green_500
|
|
13 -> R.color.lime_500
|
|
14 -> R.color.yellow_500
|
|
15 -> R.color.amber_500
|
|
16 -> R.color.orange_500
|
|
17 -> R.color.deep_orange_500
|
|
18 -> R.color.brown_500
|
|
19 -> R.color.grey_500
|
|
20 -> R.color.white_100
|
|
else -> def
|
|
}
|
|
}
|
|
}
|
|
} |