Add migration to rewrite pending commands to use folder IDs

This commit is contained in:
cketti 2020-04-25 02:21:21 +02:00
parent 5c10cfc6e6
commit c5e5b7b4f1
11 changed files with 216 additions and 25 deletions

View file

@ -12,7 +12,7 @@ import timber.log.Timber;
class StoreSchemaDefinition implements SchemaDefinition {
static final int DB_VERSION = 72;
static final int DB_VERSION = 73;
private final MigrationsHelper migrationsHelper;

View file

@ -0,0 +1,6 @@
package com.fsck.k9.storage.migrations;
class LegacyPendingAppend extends LegacyPendingCommand {
public String folder;
public String uid;
}

View file

@ -0,0 +1,4 @@
package com.fsck.k9.storage.migrations;
abstract class LegacyPendingCommand {
}

View file

@ -5,7 +5,7 @@ import java.util.List;
import static com.fsck.k9.controller.Preconditions.requireValidUids;
import static com.fsck.k9.helper.Preconditions.checkNotNull;
class LegacyPendingDelete {
class LegacyPendingDelete extends LegacyPendingCommand {
public final String folder;
public final List<String> uids;

View file

@ -0,0 +1,5 @@
package com.fsck.k9.storage.migrations;
class LegacyPendingExpunge extends LegacyPendingCommand {
public String folder;
}

View file

@ -0,0 +1,5 @@
package com.fsck.k9.storage.migrations;
class LegacyPendingMarkAllAsRead extends LegacyPendingCommand {
public String folder;
}

View file

@ -0,0 +1,9 @@
package com.fsck.k9.storage.migrations;
import java.util.Map;
class LegacyPendingMoveAndMarkAsRead extends LegacyPendingCommand {
public String srcFolder;
public String destFolder;
public Map<String, String> newUidMap;
}

View file

@ -0,0 +1,10 @@
package com.fsck.k9.storage.migrations;
import java.util.Map;
class LegacyPendingMoveOrCopy extends LegacyPendingCommand {
public String srcFolder;
public String destFolder;
public boolean isCopy;
public Map<String, String> newUidMap;
}

View file

@ -4,27 +4,9 @@ import com.fsck.k9.mail.Flag;
import java.util.List;
import static com.fsck.k9.controller.Preconditions.requireValidUids;
import static com.fsck.k9.helper.Preconditions.checkNotNull;
class LegacyPendingSetFlag {
public final String folder;
public final boolean newState;
public final Flag flag;
public final List<String> uids;
public static LegacyPendingSetFlag create(String folder, boolean newState, Flag flag, List<String> uids) {
checkNotNull(folder);
checkNotNull(flag);
requireValidUids(uids);
return new LegacyPendingSetFlag(folder, newState, flag, uids);
}
private LegacyPendingSetFlag(String folder, boolean newState, Flag flag, List<String> uids) {
this.folder = folder;
this.newState = newState;
this.flag = flag;
this.uids = uids;
}
class LegacyPendingSetFlag extends LegacyPendingCommand {
public String folder;
public boolean newState;
public Flag flag;
public List<String> uids;
}

View file

@ -0,0 +1,169 @@
package com.fsck.k9.storage.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend
import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand
import com.fsck.k9.controller.MessagingControllerCommands.PendingDelete
import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge
import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveAndMarkAsRead
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag
import com.fsck.k9.controller.PendingCommandSerializer
import com.fsck.k9.helper.map
import com.squareup.moshi.Moshi
import timber.log.Timber
internal class MigrationTo73(private val db: SQLiteDatabase) {
private val serializer = PendingCommandSerializer.getInstance()
private val moshi = Moshi.Builder().build()
private val legacyAdapters = listOf(
"append" to moshi.adapter(LegacyPendingAppend::class.java),
"mark_all_as_read" to moshi.adapter(LegacyPendingMarkAllAsRead::class.java),
"set_flag" to moshi.adapter(LegacyPendingSetFlag::class.java),
"delete" to moshi.adapter(LegacyPendingDelete::class.java),
"expunge" to moshi.adapter(LegacyPendingExpunge::class.java),
"move_or_copy" to moshi.adapter(LegacyPendingMoveOrCopy::class.java),
"move_and_mark_as_read" to moshi.adapter(LegacyPendingMoveAndMarkAsRead::class.java)
).toMap()
fun rewritePendingCommandsToUseFolderIds() {
val pendingCommands = loadPendingCommands()
rewritePendingCommands(pendingCommands)
}
private fun loadPendingCommands(): Map<Long, LegacyPendingCommand> {
return db.rawQuery(
"SELECT id, command, data FROM pending_commands WHERE command != 'empty_trash'",
null
).use { cursor ->
cursor.map {
val commandId = cursor.getLong(0)
val command = cursor.getString(1)
val data = cursor.getString(2)
val pendingCommand = deserialize(command, data)
commandId to pendingCommand
}.toMap()
}
}
private fun rewritePendingCommands(pendingCommands: Map<Long, LegacyPendingCommand>) {
for ((commandId, pendingCommand) in pendingCommands) {
when (pendingCommand) {
is LegacyPendingAppend -> rewritePendingAppend(commandId, pendingCommand)
is LegacyPendingMarkAllAsRead -> rewritePendingMarkAllAsRead(commandId, pendingCommand)
is LegacyPendingSetFlag -> rewritePendingSetFlag(commandId, pendingCommand)
is LegacyPendingDelete -> rewritePendingDelete(commandId, pendingCommand)
is LegacyPendingExpunge -> rewritePendingExpunge(commandId, pendingCommand)
is LegacyPendingMoveOrCopy -> rewritePendingMoveOrCopy(commandId, pendingCommand)
is LegacyPendingMoveAndMarkAsRead -> rewritePendingMoveAndMarkAsRead(commandId, pendingCommand)
}
}
}
private fun rewritePendingAppend(commandId: Long, legacyPendingCommand: LegacyPendingAppend) {
rewriteOrRemovePendingCommand(commandId, legacyPendingCommand.folder) { (folderId) ->
PendingAppend.create(folderId, legacyPendingCommand.uid)
}
}
private fun rewritePendingMarkAllAsRead(commandId: Long, legacyPendingCommand: LegacyPendingMarkAllAsRead) {
rewriteOrRemovePendingCommand(commandId, legacyPendingCommand.folder) { (folderId) ->
PendingMarkAllAsRead.create(folderId)
}
}
private fun rewritePendingSetFlag(commandId: Long, legacyPendingCommand: LegacyPendingSetFlag) {
rewriteOrRemovePendingCommand(commandId, legacyPendingCommand.folder) { (folderId) ->
PendingSetFlag.create(
folderId,
legacyPendingCommand.newState,
legacyPendingCommand.flag,
legacyPendingCommand.uids
)
}
}
private fun rewritePendingDelete(commandId: Long, legacyPendingCommand: LegacyPendingDelete) {
rewriteOrRemovePendingCommand(commandId, legacyPendingCommand.folder) { (folderId) ->
PendingDelete.create(folderId, legacyPendingCommand.uids)
}
}
private fun rewritePendingExpunge(commandId: Long, legacyPendingCommand: LegacyPendingExpunge) {
rewriteOrRemovePendingCommand(commandId, legacyPendingCommand.folder) { (folderId) ->
PendingExpunge.create(folderId)
}
}
private fun rewritePendingMoveOrCopy(commandId: Long, legacyPendingCommand: LegacyPendingMoveOrCopy) {
rewriteOrRemovePendingCommand(
commandId,
legacyPendingCommand.srcFolder,
legacyPendingCommand.destFolder
) { (srcFolderId, destFolderId) ->
PendingMoveOrCopy.create(
srcFolderId,
destFolderId,
legacyPendingCommand.isCopy,
legacyPendingCommand.newUidMap
)
}
}
private fun rewritePendingMoveAndMarkAsRead(commandId: Long, legacyPendingCommand: LegacyPendingMoveAndMarkAsRead) {
rewriteOrRemovePendingCommand(
commandId,
legacyPendingCommand.srcFolder,
legacyPendingCommand.destFolder
) { (srcFolderId, destFolderId) ->
PendingMoveAndMarkAsRead.create(srcFolderId, destFolderId, legacyPendingCommand.newUidMap)
}
}
private fun rewriteOrRemovePendingCommand(
commandId: Long,
vararg folderServerIds: String,
convertPendingCommand: (folderIds: List<Long>) -> PendingCommand
) {
val folderIds = folderServerIds.map {
loadFolderId(it)
}
if (folderIds.any { it == null }) {
Timber.w("Couldn't find folder ID for pending command with database ID $commandId. Removing entry.")
removePendingCommand(commandId)
} else {
val pendingCommand = convertPendingCommand(folderIds.filterNotNull())
updatePendingCommand(commandId, pendingCommand)
}
}
private fun updatePendingCommand(commandId: Long, pendingCommand: PendingCommand) {
val contentValues = ContentValues().apply {
put("data", serializer.serialize(pendingCommand))
}
db.update("pending_commands", contentValues, "id = ?", arrayOf(commandId.toString()))
}
private fun removePendingCommand(commandId: Long) {
db.delete("pending_commands", "id = ?", arrayOf(commandId.toString()))
}
private fun loadFolderId(folderServerId: String): Long? {
return db.rawQuery("SELECT id from folders WHERE server_id = ?", arrayOf(folderServerId)).use { cursor ->
if (cursor.moveToFirst()) {
cursor.getLong(0)
} else {
null
}
}
}
private fun deserialize(commandName: String, data: String): LegacyPendingCommand {
val adapter = legacyAdapters[commandName] ?: error("Unsupported pending command type!")
return adapter.fromJson(data) ?: error("Error deserializing pending command")
}
}

View file

@ -18,5 +18,6 @@ object Migrations {
if (oldVersion < 70) MigrationTo70(db).removePushState()
if (oldVersion < 71) MigrationTo71(db).cleanUpFolderClass()
if (oldVersion < 72) MigrationTo72(db).createMessagePartsRootIndex()
if (oldVersion < 73) MigrationTo73(db).rewritePendingCommandsToUseFolderIds()
}
}