Loading app/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java +1 −1 Original line number Diff line number Diff line Loading @@ -12,7 +12,7 @@ import timber.log.Timber; class StoreSchemaDefinition implements SchemaDefinition { static final int DB_VERSION = 75; static final int DB_VERSION = 76; private final MigrationsHelper migrationsHelper; Loading app/storage/src/main/java/com/fsck/k9/storage/migrations/MigrationTo76.kt 0 → 100644 +130 −0 Original line number Diff line number Diff line package com.fsck.k9.storage.migrations import android.content.ContentValues import android.database.sqlite.SQLiteDatabase import com.fsck.k9.Account import com.fsck.k9.helper.map import com.fsck.k9.mailstore.MigrationsHelper import timber.log.Timber /** * Clean up special local folders * * In the past local special folders were not always created. For example, when importing settings or when setting up * an account, but checking the server settings didn't succeed and the user decided to continue anyway. * * Clicking "Next" in the incoming server settings screen would check the server settings and, in the case of success, * create new special local folders even if they already existed. So it's also possible existing installations have * multiple special local folders of one type. * * Here, we clean up local special folders to have exactly one of each type. Messages in additional folders will be * moved to the folder we keep and then the other folders will be deleted. An exception are messages in old Outbox * folders. They will be deleted and not be moved to the new/current Outbox folder because this would cause potentially * very old messages to be sent. The right thing would be to move them to the Drafts folder. But this is much more * complicated. They'd have to be uploaded if the Drafts folder is not a local folder. It's also not clear what should * happen if there is no Drafts folder configured. */ internal class MigrationTo76(private val db: SQLiteDatabase, private val migrationsHelper: MigrationsHelper) { fun cleanUpSpecialLocalFolders() { val account = migrationsHelper.account Timber.v("Cleaning up Outbox folder") val outboxFolderId = account.outboxFolderId ?: createFolder("Outbox", "K9MAIL_INTERNAL_OUTBOX", OUTBOX_FOLDER_TYPE) deleteOtherOutboxFolders(outboxFolderId) account.outboxFolderId = outboxFolderId if (account.isPop3()) { Timber.v("Cleaning up Drafts folder") val draftsFolderId = account.draftsFolderId ?: createFolder("Drafts", "Drafts", DRAFTS_FOLDER_TYPE) moveMessages(DRAFTS_FOLDER_TYPE, draftsFolderId) account.draftsFolderId = draftsFolderId Timber.v("Cleaning up Sent folder") val sentFolderId = account.sentFolderId ?: createFolder("Sent", "Sent", SENT_FOLDER_TYPE) moveMessages(SENT_FOLDER_TYPE, sentFolderId) account.sentFolderId = sentFolderId Timber.v("Cleaning up Trash folder") val trashFolderId = account.trashFolderId ?: createFolder("Trash", "Trash", TRASH_FOLDER_TYPE) moveMessages(TRASH_FOLDER_TYPE, trashFolderId) account.trashFolderId = trashFolderId } migrationsHelper.saveAccount() } private fun createFolder(name: String, serverId: String, type: String): Long { Timber.v(" Creating new local folder (name=$name, serverId=$serverId, type=$type)…") val values = ContentValues().apply { put("name", name) put("visible_limit", 25) put("integrate", 0) put("top_group", 0) put("poll_class", "NO_CLASS") put("push_class", "SECOND_CLASS") put("display_class", "NO_CLASS") put("server_id", serverId) put("local_only", 1) put("type", type) } val folderId = db.insert("folders", null, values) Timber.v(" Created folder with ID $folderId") return folderId } private fun deleteOtherOutboxFolders(outboxFolderId: Long) { val otherFolderIds = getOtherFolders(OUTBOX_FOLDER_TYPE, outboxFolderId) for (folderId in otherFolderIds) { deleteFolder(folderId) } } private fun getOtherFolders(folderType: String, excludeFolderId: Long): List<Long> { return db.query( "folders", arrayOf("id"), "local_only = 1 AND type = ? AND id != ?", arrayOf(folderType, excludeFolderId.toString()), null, null, null ).use { cursor -> cursor.map { cursor.getLong(0) } } } private fun moveMessages(folderType: String, destinationFolderId: Long) { val sourceFolderIds = getOtherFolders(folderType, destinationFolderId) for (sourceFolderId in sourceFolderIds) { moveMessages(sourceFolderId, destinationFolderId) deleteFolder(sourceFolderId) } } private fun moveMessages(sourceFolderId: Long, destinationFolderId: Long) { Timber.v(" Moving messages from folder [$sourceFolderId] to folder [$destinationFolderId]…") val values = ContentValues().apply { put("folder_id", destinationFolderId) } val rows = db.update("messages", values, "folder_id = ?", arrayOf(sourceFolderId.toString())) Timber.v(" $rows messages moved.") } private fun deleteFolder(folderId: Long) { Timber.v(" Deleting folder [$folderId]") db.delete("folders", "id = ?", arrayOf(folderId.toString())) } private fun Account.isPop3() = storeUri.startsWith("pop3") companion object { private const val OUTBOX_FOLDER_TYPE = "outbox" private const val DRAFTS_FOLDER_TYPE = "drafts" private const val SENT_FOLDER_TYPE = "sent" private const val TRASH_FOLDER_TYPE = "trash" } } app/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt +1 −0 Original line number Diff line number Diff line Loading @@ -21,5 +21,6 @@ object Migrations { if (oldVersion < 73) MigrationTo73(db).rewritePendingCommandsToUseFolderIds() if (oldVersion < 74) MigrationTo74(db, migrationsHelper.account).removeDeletedMessages() if (oldVersion < 75) MigrationTo75(db, migrationsHelper).updateAccountWithSpecialFolderIds() if (oldVersion < 76) MigrationTo76(db, migrationsHelper).cleanUpSpecialLocalFolders() } } Loading
app/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java +1 −1 Original line number Diff line number Diff line Loading @@ -12,7 +12,7 @@ import timber.log.Timber; class StoreSchemaDefinition implements SchemaDefinition { static final int DB_VERSION = 75; static final int DB_VERSION = 76; private final MigrationsHelper migrationsHelper; Loading
app/storage/src/main/java/com/fsck/k9/storage/migrations/MigrationTo76.kt 0 → 100644 +130 −0 Original line number Diff line number Diff line package com.fsck.k9.storage.migrations import android.content.ContentValues import android.database.sqlite.SQLiteDatabase import com.fsck.k9.Account import com.fsck.k9.helper.map import com.fsck.k9.mailstore.MigrationsHelper import timber.log.Timber /** * Clean up special local folders * * In the past local special folders were not always created. For example, when importing settings or when setting up * an account, but checking the server settings didn't succeed and the user decided to continue anyway. * * Clicking "Next" in the incoming server settings screen would check the server settings and, in the case of success, * create new special local folders even if they already existed. So it's also possible existing installations have * multiple special local folders of one type. * * Here, we clean up local special folders to have exactly one of each type. Messages in additional folders will be * moved to the folder we keep and then the other folders will be deleted. An exception are messages in old Outbox * folders. They will be deleted and not be moved to the new/current Outbox folder because this would cause potentially * very old messages to be sent. The right thing would be to move them to the Drafts folder. But this is much more * complicated. They'd have to be uploaded if the Drafts folder is not a local folder. It's also not clear what should * happen if there is no Drafts folder configured. */ internal class MigrationTo76(private val db: SQLiteDatabase, private val migrationsHelper: MigrationsHelper) { fun cleanUpSpecialLocalFolders() { val account = migrationsHelper.account Timber.v("Cleaning up Outbox folder") val outboxFolderId = account.outboxFolderId ?: createFolder("Outbox", "K9MAIL_INTERNAL_OUTBOX", OUTBOX_FOLDER_TYPE) deleteOtherOutboxFolders(outboxFolderId) account.outboxFolderId = outboxFolderId if (account.isPop3()) { Timber.v("Cleaning up Drafts folder") val draftsFolderId = account.draftsFolderId ?: createFolder("Drafts", "Drafts", DRAFTS_FOLDER_TYPE) moveMessages(DRAFTS_FOLDER_TYPE, draftsFolderId) account.draftsFolderId = draftsFolderId Timber.v("Cleaning up Sent folder") val sentFolderId = account.sentFolderId ?: createFolder("Sent", "Sent", SENT_FOLDER_TYPE) moveMessages(SENT_FOLDER_TYPE, sentFolderId) account.sentFolderId = sentFolderId Timber.v("Cleaning up Trash folder") val trashFolderId = account.trashFolderId ?: createFolder("Trash", "Trash", TRASH_FOLDER_TYPE) moveMessages(TRASH_FOLDER_TYPE, trashFolderId) account.trashFolderId = trashFolderId } migrationsHelper.saveAccount() } private fun createFolder(name: String, serverId: String, type: String): Long { Timber.v(" Creating new local folder (name=$name, serverId=$serverId, type=$type)…") val values = ContentValues().apply { put("name", name) put("visible_limit", 25) put("integrate", 0) put("top_group", 0) put("poll_class", "NO_CLASS") put("push_class", "SECOND_CLASS") put("display_class", "NO_CLASS") put("server_id", serverId) put("local_only", 1) put("type", type) } val folderId = db.insert("folders", null, values) Timber.v(" Created folder with ID $folderId") return folderId } private fun deleteOtherOutboxFolders(outboxFolderId: Long) { val otherFolderIds = getOtherFolders(OUTBOX_FOLDER_TYPE, outboxFolderId) for (folderId in otherFolderIds) { deleteFolder(folderId) } } private fun getOtherFolders(folderType: String, excludeFolderId: Long): List<Long> { return db.query( "folders", arrayOf("id"), "local_only = 1 AND type = ? AND id != ?", arrayOf(folderType, excludeFolderId.toString()), null, null, null ).use { cursor -> cursor.map { cursor.getLong(0) } } } private fun moveMessages(folderType: String, destinationFolderId: Long) { val sourceFolderIds = getOtherFolders(folderType, destinationFolderId) for (sourceFolderId in sourceFolderIds) { moveMessages(sourceFolderId, destinationFolderId) deleteFolder(sourceFolderId) } } private fun moveMessages(sourceFolderId: Long, destinationFolderId: Long) { Timber.v(" Moving messages from folder [$sourceFolderId] to folder [$destinationFolderId]…") val values = ContentValues().apply { put("folder_id", destinationFolderId) } val rows = db.update("messages", values, "folder_id = ?", arrayOf(sourceFolderId.toString())) Timber.v(" $rows messages moved.") } private fun deleteFolder(folderId: Long) { Timber.v(" Deleting folder [$folderId]") db.delete("folders", "id = ?", arrayOf(folderId.toString())) } private fun Account.isPop3() = storeUri.startsWith("pop3") companion object { private const val OUTBOX_FOLDER_TYPE = "outbox" private const val DRAFTS_FOLDER_TYPE = "drafts" private const val SENT_FOLDER_TYPE = "sent" private const val TRASH_FOLDER_TYPE = "trash" } }
app/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt +1 −0 Original line number Diff line number Diff line Loading @@ -21,5 +21,6 @@ object Migrations { if (oldVersion < 73) MigrationTo73(db).rewritePendingCommandsToUseFolderIds() if (oldVersion < 74) MigrationTo74(db, migrationsHelper.account).removeDeletedMessages() if (oldVersion < 75) MigrationTo75(db, migrationsHelper).updateAccountWithSpecialFolderIds() if (oldVersion < 76) MigrationTo76(db, migrationsHelper).cleanUpSpecialLocalFolders() } }