Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions feature/migration/qrcode/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ android {

dependencies {
implementation(projects.core.common)
implementation(projects.legacy.account)
implementation(projects.legacy.common)
implementation(projects.legacy.ui.base)
implementation(projects.core.ui.compose.designsystem)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package app.k9mail.feature.migration.qrcode
import app.k9mail.feature.migration.qrcode.domain.QrCodeDomainContract.UseCase
import app.k9mail.feature.migration.qrcode.domain.usecase.QrCodePayloadReader
import app.k9mail.feature.migration.qrcode.domain.usecase.QrCodeSettingsWriter
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadAdapter
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadMapper
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadParser
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadValidator
import app.k9mail.feature.migration.qrcode.settings.DefaultUuidGenerator
import app.k9mail.feature.migration.qrcode.settings.UuidGenerator
import app.k9mail.feature.migration.qrcode.settings.XmlSettingWriter
Expand All @@ -18,7 +22,23 @@ val qrCodeModule = module {
)
}

factory<UseCase.QrCodePayloadReader> { QrCodePayloadReader() }
factory { QrCodePayloadAdapter() }
factory { QrCodePayloadParser(qrCodePayloadAdapter = get()) }
factory { QrCodePayloadValidator() }
factory {
QrCodePayloadMapper(
qrCodePayloadValidator = get(),
deletePolicyProvider = get(),
)
}

factory<UseCase.QrCodePayloadReader> {
QrCodePayloadReader(
parser = get(),
mapper = get(),
)
}

factory<UseCase.QrCodeSettingsWriter> {
QrCodeSettingsWriter(
context = get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package app.k9mail.feature.migration.qrcode.domain.entity
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Hostname
import app.k9mail.core.common.net.Port
import app.k9mail.legacy.account.Account
import app.k9mail.legacy.account.Account.DeletePolicy

internal data class AccountData(
val sequenceNumber: Int,
Expand All @@ -11,6 +13,7 @@ internal data class AccountData(
) {
data class Account(
val accountName: String,
val deletePolicy: DeletePolicy,
val incomingServer: IncomingServer,
val outgoingServerGroups: List<OutgoingServerGroup>,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadMapper
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadParser

internal class QrCodePayloadReader(
private val parser: QrCodePayloadParser = QrCodePayloadParser(),
private val mapper: QrCodePayloadMapper = QrCodePayloadMapper(),
private val parser: QrCodePayloadParser,
private val mapper: QrCodePayloadMapper,
) : UseCase.QrCodePayloadReader {
override fun read(payload: String): AccountData? {
val parsedData = parser.parse(payload) ?: return null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package app.k9mail.feature.migration.qrcode.payload

import app.k9mail.core.common.mail.Protocols
import app.k9mail.core.common.mail.toUserEmailAddress
import app.k9mail.core.common.net.toHostname
import app.k9mail.core.common.net.toPort
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.IncomingServerProtocol
import app.k9mail.legacy.account.Account.DeletePolicy
import com.fsck.k9.account.DeletePolicyProvider

internal class QrCodePayloadMapper(
private val qrCodePayloadValidator: QrCodePayloadValidator = QrCodePayloadValidator(),
private val qrCodePayloadValidator: QrCodePayloadValidator,
private val deletePolicyProvider: DeletePolicyProvider,
) {
fun toAccountData(data: QrCodeData): AccountData? {
return if (qrCodePayloadValidator.isValid(data)) {
Expand All @@ -31,9 +36,11 @@ internal class QrCodePayloadMapper(
accountName = account.incomingServer.accountName,
identity = outgoingServerGroups.first().identities.first(),
)
val deletePolicy = getDeletePolicy(incomingServer.protocol)

return AccountData.Account(
accountName = accountName,
deletePolicy = deletePolicy,
incomingServer = incomingServer,
outgoingServerGroups = outgoingServerGroups,
)
Expand Down Expand Up @@ -92,4 +99,13 @@ internal class QrCodePayloadMapper(
displayName = identity.displayName,
)
}

private fun getDeletePolicy(protocol: IncomingServerProtocol): DeletePolicy {
val accountType = when (protocol) {
IncomingServerProtocol.Imap -> Protocols.IMAP
IncomingServerProtocol.Pop3 -> Protocols.POP3
}

return deletePolicyProvider.getDeletePolicy(accountType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.io.IOException
import timber.log.Timber

internal class QrCodePayloadParser(
private val qrCodePayloadAdapter: QrCodePayloadAdapter = QrCodePayloadAdapter(),
private val qrCodePayloadAdapter: QrCodePayloadAdapter,
) {
/**
* Parses the QR code payload as JSON and reads it into [QrCodeData].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.Identity
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.IncomingServer
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.OutgoingServer
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.OutgoingServerGroup
import app.k9mail.legacy.account.Account.DeletePolicy
import java.io.OutputStream
import org.xmlpull.v1.XmlSerializer

// TODO: This duplicates much of the code in SettingsExporter. Add an abstraction layer for the input data, so we can
// use a single XML writer class for exporting accounts and writing QR code payloads to a settings file.
@Suppress("TooManyFunctions")
internal class XmlSettingWriter(
private val uuidGenerator: UuidGenerator,
) {
Expand Down Expand Up @@ -56,12 +58,28 @@ internal class XmlSettingWriter(
attribute(null, UUID_ATTRIBUTE, accountUuid)

writeElement(NAME_ELEMENT, account.accountName)
writeSettings(account)
writeIncomingServer(account.incomingServer)
writeOutgoingServers(account.outgoingServerGroups)

endTag(null, ACCOUNT_ELEMENT)
}

private fun XmlSerializer.writeSettings(account: Account) {
startTag(null, SETTINGS_ELEMENT)
writeKeyValue("deletePolicy", account.deletePolicy.toSettingsFileValue())
endTag(null, SETTINGS_ELEMENT)
}

private fun XmlSerializer.writeKeyValue(key: String, value: String?) {
startTag(null, VALUE_ELEMENT)
attribute(null, KEY_ATTRIBUTE, key)
if (value != null) {
text(value)
}
endTag(null, VALUE_ELEMENT)
}

private fun XmlSerializer.writeIncomingServer(incomingServer: IncomingServer) {
startTag(null, INCOMING_SERVER_ELEMENT)
attribute(null, TYPE_ATTRIBUTE, incomingServer.protocol.mapToSettingsString())
Expand Down Expand Up @@ -139,6 +157,9 @@ internal class XmlSettingWriter(
private const val ACCOUNTS_ELEMENT = "accounts"
private const val ACCOUNT_ELEMENT = "account"
private const val UUID_ATTRIBUTE = "uuid"
private const val SETTINGS_ELEMENT = "settings"
private const val VALUE_ELEMENT = "value"
private const val KEY_ATTRIBUTE = "key"
private const val INCOMING_SERVER_ELEMENT = "incoming-server"
private const val OUTGOING_SERVER_ELEMENT = "outgoing-server"
private const val TYPE_ATTRIBUTE = "type"
Expand All @@ -154,3 +175,12 @@ internal class XmlSettingWriter(
private const val EMAIL_ELEMENT = "email"
}
}

private fun DeletePolicy.toSettingsFileValue(): String {
return when (this) {
DeletePolicy.NEVER -> "NEVER"
DeletePolicy.SEVEN_DAYS -> error("Unsupported value")
DeletePolicy.ON_DELETE -> "DELETE"
DeletePolicy.MARK_AS_READ -> "MARK_AS_READ"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import app.k9mail.core.common.mail.toUserEmailAddress
import app.k9mail.core.common.net.toHostname
import app.k9mail.core.common.net.toPort
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData
import app.k9mail.feature.migration.qrcode.payload.FakeDeletePolicyProvider
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadAdapter
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadMapper
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadParser
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadValidator
import app.k9mail.legacy.account.Account
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
Expand All @@ -12,7 +18,13 @@ import kotlin.test.Test

@Suppress("LongMethod")
class QrCodePayloadReaderTest {
private val reader = QrCodePayloadReader()
private val reader = QrCodePayloadReader(
parser = QrCodePayloadParser(QrCodePayloadAdapter()),
mapper = QrCodePayloadMapper(
qrCodePayloadValidator = QrCodePayloadValidator(),
deletePolicyProvider = FakeDeletePolicyProvider(),
),
)

@Test
fun `one account, one identity, no passwords`() {
Expand All @@ -30,6 +42,7 @@ class QrCodePayloadReaderTest {
accounts = listOf(
AccountData.Account(
accountName = "My Account",
deletePolicy = FakeDeletePolicyProvider.DELETE_POLICY,
incomingServer = AccountData.IncomingServer(
protocol = AccountData.IncomingServerProtocol.Imap,
hostname = "imap.domain.example".toHostname(),
Expand Down Expand Up @@ -85,6 +98,7 @@ class QrCodePayloadReaderTest {
accounts = listOf(
AccountData.Account(
accountName = "[email protected]",
deletePolicy = FakeDeletePolicyProvider.DELETE_POLICY,
incomingServer = AccountData.IncomingServer(
protocol = AccountData.IncomingServerProtocol.Imap,
hostname = "imap.domain.example".toHostname(),
Expand Down Expand Up @@ -120,6 +134,7 @@ class QrCodePayloadReaderTest {
),
AccountData.Account(
accountName = "[email protected]",
deletePolicy = FakeDeletePolicyProvider.DELETE_POLICY,
incomingServer = AccountData.IncomingServer(
protocol = AccountData.IncomingServerProtocol.Imap,
hostname = "imap.company.example".toHostname(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.k9mail.feature.migration.qrcode.payload

import app.k9mail.legacy.account.Account.DeletePolicy
import com.fsck.k9.account.DeletePolicyProvider

class FakeDeletePolicyProvider : DeletePolicyProvider {
override fun getDeletePolicy(accountType: String): DeletePolicy {
return DELETE_POLICY
}

companion object {
val DELETE_POLICY = DeletePolicy.ON_DELETE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import app.k9mail.core.common.net.toHostname
import app.k9mail.core.common.net.toPort
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.ConnectionSecurity
import app.k9mail.legacy.account.Account
import assertk.assertThat
import assertk.assertions.first
import assertk.assertions.isEqualTo
Expand All @@ -13,7 +14,10 @@ import assertk.assertions.prop
import kotlin.test.Test

class QrCodePayloadMapperTest {
private val mapper = QrCodePayloadMapper()
private val mapper = QrCodePayloadMapper(
qrCodePayloadValidator = QrCodePayloadValidator(),
deletePolicyProvider = FakeDeletePolicyProvider(),
)

@Test
fun `valid input should be mapped to expected output`() {
Expand Down Expand Up @@ -111,6 +115,7 @@ class QrCodePayloadMapperTest {
accounts = listOf(
AccountData.Account(
accountName = "Account name",
deletePolicy = FakeDeletePolicyProvider.DELETE_POLICY,
incomingServer = AccountData.IncomingServer(
protocol = AccountData.IncomingServerProtocol.Imap,
hostname = "imap.domain.example".toHostname(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlin.test.Test

@Suppress("LongMethod")
class QrCodePayloadParserTest {
private val parser = QrCodePayloadParser()
private val parser = QrCodePayloadParser(QrCodePayloadAdapter())

@Test
fun `one account, one identity, no account name, no passwords`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import app.k9mail.core.common.net.toHostname
import app.k9mail.core.common.net.toPort
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData
import app.k9mail.feature.migration.qrcode.domain.entity.AccountData.ConnectionSecurity
import app.k9mail.legacy.account.Account
import app.k9mail.legacy.account.Account.DeletePolicy
import assertk.assertThat
import assertk.assertions.isEqualTo
import kotlin.test.Test
Expand Down Expand Up @@ -36,6 +38,7 @@ class XmlSettingWriterTest {
companion object {
private val ACCOUNT = AccountData.Account(
accountName = "Account name",
deletePolicy = DeletePolicy.ON_DELETE,
incomingServer = AccountData.IncomingServer(
protocol = AccountData.IncomingServerProtocol.Imap,
hostname = "imap.domain.example".toHostname(),
Expand Down Expand Up @@ -73,6 +76,9 @@ class XmlSettingWriterTest {
<accounts>
<account uuid="test-uuid">
<name>Account name</name>
<settings>
<value key="deletePolicy">DELETE</value>
</settings>
<incoming-server type="IMAP">
<host>imap.domain.example</host>
<port>993</port>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import app.k9mail.core.ui.compose.testing.mvi.runMviTest
import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck
import app.k9mail.feature.migration.qrcode.domain.QrCodeDomainContract.UseCase
import app.k9mail.feature.migration.qrcode.domain.usecase.QrCodePayloadReader
import app.k9mail.feature.migration.qrcode.payload.FakeDeletePolicyProvider
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadAdapter
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadMapper
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadParser
import app.k9mail.feature.migration.qrcode.payload.QrCodePayloadValidator
import app.k9mail.feature.migration.qrcode.ui.QrCodeScannerContract.DisplayText
import app.k9mail.feature.migration.qrcode.ui.QrCodeScannerContract.Effect
import app.k9mail.feature.migration.qrcode.ui.QrCodeScannerContract.Event
Expand Down Expand Up @@ -135,7 +140,13 @@ private class QrCodeScannerScreenRobot(
) {
private val qrCodeSettingsWriter = FakeQrCodeSettingsWriter()
private val viewModel = QrCodeScannerViewModel(
qrCodePayloadReader = QrCodePayloadReader(),
qrCodePayloadReader = QrCodePayloadReader(
parser = QrCodePayloadParser(QrCodePayloadAdapter()),
mapper = QrCodePayloadMapper(
qrCodePayloadValidator = QrCodePayloadValidator(),
deletePolicyProvider = FakeDeletePolicyProvider(),
),
),
qrCodeSettingsWriter = qrCodeSettingsWriter,
createCameraUseCaseProvider = { listener ->
qrCodeListener = listener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class AccountCreator(
private val preferences: Preferences,
private val context: Context,
private val messagingController: MessagingController,
private val deletePolicyProvider: DeletePolicyProvider,
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : AccountSetupExternalContract.AccountCreator {

Expand Down Expand Up @@ -67,7 +68,7 @@ class AccountCreator(
newAccount.automaticCheckIntervalMinutes = account.options.checkFrequencyInMinutes
newAccount.displayCount = account.options.messageDisplayCount

newAccount.deletePolicy = DeletePolicyHelper.getDefaultDeletePolicy(newAccount.incomingServerSettings.type)
newAccount.deletePolicy = deletePolicyProvider.getDeletePolicy(newAccount.incomingServerSettings.type)
newAccount.chipColor = accountColorPicker.pickColor()

localFoldersCreator.createSpecialLocalFolders(newAccount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ val newAccountModule = module {
localFoldersCreator = get(),
preferences = get(),
context = androidApplication(),
deletePolicyProvider = get(),
messagingController = get(),
)
}
Expand All @@ -50,4 +51,6 @@ val newAccountModule = module {
messagingController = get(),
)
}

factory<DeletePolicyProvider> { DefaultDeletePolicyProvider() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fsck.k9.account

import app.k9mail.core.common.mail.Protocols
import app.k9mail.legacy.account.Account.DeletePolicy

class DefaultDeletePolicyProvider : DeletePolicyProvider {
override fun getDeletePolicy(accountType: String): DeletePolicy {
return when (accountType) {
Protocols.IMAP -> DeletePolicy.ON_DELETE
Protocols.POP3 -> DeletePolicy.NEVER
"demo" -> DeletePolicy.ON_DELETE
else -> throw AssertionError("Unhandled case: $accountType")
}
}
}
Loading