Add ImapBackendPusher that manages multiple ImapFolderPusher instances
This commit is contained in:
parent
d62aa030c9
commit
c8808ae447
7 changed files with 217 additions and 6 deletions
|
@ -10,6 +10,7 @@ import com.fsck.k9.mail.NetworkType
|
|||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.power.PowerManager
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.ImapStore
|
||||
import com.fsck.k9.mail.store.imap.ImapStoreConfig
|
||||
import com.fsck.k9.mail.transport.smtp.SmtpTransport
|
||||
|
@ -18,6 +19,7 @@ import com.fsck.k9.mailstore.K9BackendStorageFactory
|
|||
class ImapBackendFactory(
|
||||
private val context: Context,
|
||||
private val powerManager: PowerManager,
|
||||
private val idleRefreshManager: IdleRefreshManager,
|
||||
private val backendStorageFactory: K9BackendStorageFactory,
|
||||
private val trustedSocketFactory: TrustedSocketFactory
|
||||
) : BackendFactory {
|
||||
|
@ -26,7 +28,8 @@ class ImapBackendFactory(
|
|||
val backendStorage = backendStorageFactory.createBackendStorage(account)
|
||||
val imapStore = createImapStore(account)
|
||||
val smtpTransport = createSmtpTransport(account)
|
||||
return ImapBackend(accountName, backendStorage, imapStore, powerManager, smtpTransport)
|
||||
|
||||
return ImapBackend(accountName, backendStorage, imapStore, powerManager, idleRefreshManager, smtpTransport)
|
||||
}
|
||||
|
||||
private fun createImapStore(account: Account): ImapStore {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.fsck.k9.backends
|
||||
|
||||
import com.fsck.k9.backend.BackendManager
|
||||
import com.fsck.k9.backend.imap.BackendIdleRefreshManager
|
||||
import com.fsck.k9.backend.jmap.JmapAccountDiscovery
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import org.koin.dsl.module
|
||||
|
||||
val backendsModule = module {
|
||||
|
@ -15,7 +17,16 @@ val backendsModule = module {
|
|||
)
|
||||
)
|
||||
}
|
||||
single { ImapBackendFactory(get(), get(), get(), get()) }
|
||||
single {
|
||||
ImapBackendFactory(
|
||||
context = get(),
|
||||
powerManager = get(),
|
||||
idleRefreshManager = get(),
|
||||
backendStorageFactory = get(),
|
||||
trustedSocketFactory = get()
|
||||
)
|
||||
}
|
||||
single<IdleRefreshManager> { BackendIdleRefreshManager() }
|
||||
single { Pop3BackendFactory(get(), get()) }
|
||||
single { WebDavBackendFactory(get(), get(), get()) }
|
||||
single { JmapBackendFactory(get(), get()) }
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.fsck.k9.mail.NetworkType
|
|||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.power.PowerManager
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.ImapStore
|
||||
import com.fsck.k9.mail.store.imap.ImapStoreConfig
|
||||
import com.fsck.k9.mail.transport.smtp.SmtpTransport
|
||||
|
@ -18,6 +19,7 @@ import com.fsck.k9.mailstore.K9BackendStorageFactory
|
|||
class ImapBackendFactory(
|
||||
private val context: Context,
|
||||
private val powerManager: PowerManager,
|
||||
private val idleRefreshManager: IdleRefreshManager,
|
||||
private val backendStorageFactory: K9BackendStorageFactory,
|
||||
private val trustedSocketFactory: TrustedSocketFactory
|
||||
) : BackendFactory {
|
||||
|
@ -26,7 +28,8 @@ class ImapBackendFactory(
|
|||
val backendStorage = backendStorageFactory.createBackendStorage(account)
|
||||
val imapStore = createImapStore(account)
|
||||
val smtpTransport = createSmtpTransport(account)
|
||||
return ImapBackend(accountName, backendStorage, imapStore, powerManager, smtpTransport)
|
||||
|
||||
return ImapBackend(accountName, backendStorage, imapStore, powerManager, idleRefreshManager, smtpTransport)
|
||||
}
|
||||
|
||||
private fun createImapStore(account: Account): ImapStore {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.fsck.k9.backends
|
||||
|
||||
import com.fsck.k9.backend.BackendManager
|
||||
import com.fsck.k9.backend.imap.BackendIdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import org.koin.dsl.module
|
||||
|
||||
val backendsModule = module {
|
||||
|
@ -13,7 +15,16 @@ val backendsModule = module {
|
|||
)
|
||||
)
|
||||
}
|
||||
single { ImapBackendFactory(get(), get(), get(), get()) }
|
||||
single {
|
||||
ImapBackendFactory(
|
||||
context = get(),
|
||||
powerManager = get(),
|
||||
idleRefreshManager = get(),
|
||||
backendStorageFactory = get(),
|
||||
trustedSocketFactory = get()
|
||||
)
|
||||
}
|
||||
single<IdleRefreshManager> { BackendIdleRefreshManager() }
|
||||
single { Pop3BackendFactory(get(), get()) }
|
||||
single { WebDavBackendFactory(get(), get(), get()) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.fsck.k9.backend.imap
|
||||
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshTimer
|
||||
|
||||
class BackendIdleRefreshManager : IdleRefreshManager {
|
||||
override fun startTimer(timeout: Long, callback: () -> Unit): IdleRefreshTimer {
|
||||
TODO("implement")
|
||||
}
|
||||
|
||||
override fun resetTimers() {
|
||||
TODO("implement")
|
||||
}
|
||||
}
|
|
@ -11,14 +11,16 @@ import com.fsck.k9.mail.Flag
|
|||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.Part
|
||||
import com.fsck.k9.mail.power.PowerManager
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.ImapStore
|
||||
import com.fsck.k9.mail.transport.smtp.SmtpTransport
|
||||
|
||||
class ImapBackend(
|
||||
accountName: String,
|
||||
private val accountName: String,
|
||||
backendStorage: BackendStorage,
|
||||
private val imapStore: ImapStore,
|
||||
private val powerManager: PowerManager,
|
||||
private val idleRefreshManager: IdleRefreshManager,
|
||||
private val smtpTransport: SmtpTransport
|
||||
) : Backend {
|
||||
private val imapSync = ImapSync(accountName, backendStorage, imapStore)
|
||||
|
@ -155,6 +157,6 @@ class ImapBackend(
|
|||
}
|
||||
|
||||
override fun createPusher(callback: BackendPusherCallback): BackendPusher {
|
||||
TODO("implement")
|
||||
return ImapBackendPusher(imapStore, powerManager, idleRefreshManager, callback, accountName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
package com.fsck.k9.backend.imap
|
||||
|
||||
import com.fsck.k9.backend.api.BackendPusher
|
||||
import com.fsck.k9.backend.api.BackendPusherCallback
|
||||
import com.fsck.k9.mail.AuthenticationFailedException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.power.PowerManager
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshTimer
|
||||
import com.fsck.k9.mail.store.imap.ImapStore
|
||||
import java.io.IOException
|
||||
import timber.log.Timber
|
||||
|
||||
private const val IO_ERROR_TIMEOUT = 5 * 60 * 1000L
|
||||
private const val UNEXPECTED_ERROR_TIMEOUT = 60 * 60 * 1000L
|
||||
|
||||
/**
|
||||
* Manages [ImapFolderPusher] instances that listen for changes to individual folders.
|
||||
*/
|
||||
internal class ImapBackendPusher(
|
||||
private val imapStore: ImapStore,
|
||||
private val powerManager: PowerManager,
|
||||
private val idleRefreshManager: IdleRefreshManager,
|
||||
private val callback: BackendPusherCallback,
|
||||
private val accountName: String
|
||||
) : BackendPusher, ImapPusherCallback {
|
||||
private val lock = Any()
|
||||
private val pushFolders = mutableMapOf<String, ImapFolderPusher>()
|
||||
private var currentFolderServerIds: Collection<String> = emptySet()
|
||||
private val pushFolderSleeping = mutableMapOf<String, IdleRefreshTimer>()
|
||||
|
||||
override fun updateFolders(folderServerIds: Collection<String>) {
|
||||
Timber.v("ImapBackendPusher.updateFolders(): %s", folderServerIds)
|
||||
|
||||
val stopFolderPushers: List<ImapFolderPusher>
|
||||
val startFolderPushers: List<ImapFolderPusher>
|
||||
synchronized(lock) {
|
||||
currentFolderServerIds = folderServerIds
|
||||
|
||||
val oldRunningFolderServerIds = pushFolders.keys
|
||||
val oldFolderServerIds = oldRunningFolderServerIds + pushFolderSleeping.keys
|
||||
val removeFolderServerIds = oldFolderServerIds - folderServerIds
|
||||
stopFolderPushers = removeFolderServerIds
|
||||
.asSequence()
|
||||
.onEach { folderServerId -> cancelRetryTimer(folderServerId) }
|
||||
.map { folderServerId -> pushFolders.remove(folderServerId) }
|
||||
.filterNotNull()
|
||||
.toList()
|
||||
|
||||
val startFolderServerIds = folderServerIds - oldRunningFolderServerIds
|
||||
startFolderPushers = startFolderServerIds
|
||||
.asSequence()
|
||||
.filterNot { folderServerId -> isWaitingForRetry(folderServerId) }
|
||||
.onEach { folderServerId -> pushFolderSleeping.remove(folderServerId) }
|
||||
.map { folderServerId ->
|
||||
createImapFolderPusher(folderServerId).also { folderPusher ->
|
||||
pushFolders[folderServerId] = folderPusher
|
||||
}
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
|
||||
for (folderPusher in stopFolderPushers) {
|
||||
folderPusher.stop()
|
||||
}
|
||||
|
||||
for (folderPusher in startFolderPushers) {
|
||||
folderPusher.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
Timber.v("ImapBackendPusher.stop()")
|
||||
|
||||
synchronized(lock) {
|
||||
for (pushFolder in pushFolders.values) {
|
||||
pushFolder.stop()
|
||||
}
|
||||
pushFolders.clear()
|
||||
|
||||
for (retryTimer in pushFolderSleeping.values) {
|
||||
retryTimer.cancel()
|
||||
}
|
||||
pushFolderSleeping.clear()
|
||||
|
||||
currentFolderServerIds = emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createImapFolderPusher(folderServerId: String): ImapFolderPusher {
|
||||
// TODO: use value from account settings
|
||||
val idleRefreshTimeoutMs = 25 * 60 * 1000L
|
||||
return ImapFolderPusher(
|
||||
imapStore,
|
||||
powerManager,
|
||||
idleRefreshManager,
|
||||
this,
|
||||
accountName,
|
||||
folderServerId,
|
||||
idleRefreshTimeoutMs
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPushEvent(folderServerId: String) {
|
||||
callback.onPushEvent(folderServerId)
|
||||
idleRefreshManager.resetTimers()
|
||||
}
|
||||
|
||||
override fun onPushError(folderServerId: String, exception: Exception) {
|
||||
synchronized(lock) {
|
||||
pushFolders.remove(folderServerId)
|
||||
|
||||
when (exception) {
|
||||
is AuthenticationFailedException -> {
|
||||
Timber.v(exception, "Authentication failure when attempting to use IDLE")
|
||||
// TODO: This could be happening because of too many connections to the host. Ideally we'd want to
|
||||
// detect this case and use a lower timeout.
|
||||
|
||||
startRetryTimer(folderServerId, UNEXPECTED_ERROR_TIMEOUT)
|
||||
}
|
||||
is IOException -> {
|
||||
Timber.v(exception, "I/O error while trying to use IDLE")
|
||||
|
||||
startRetryTimer(folderServerId, IO_ERROR_TIMEOUT)
|
||||
}
|
||||
is MessagingException -> {
|
||||
Timber.v(exception, "MessagingException")
|
||||
|
||||
if (exception.isPermanentFailure) {
|
||||
startRetryTimer(folderServerId, UNEXPECTED_ERROR_TIMEOUT)
|
||||
} else {
|
||||
startRetryTimer(folderServerId, IO_ERROR_TIMEOUT)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.v(exception, "Unexpected error")
|
||||
startRetryTimer(folderServerId, UNEXPECTED_ERROR_TIMEOUT)
|
||||
}
|
||||
}
|
||||
|
||||
if (pushFolders.isEmpty()) {
|
||||
callback.onPushError(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRetryTimer(folderServerId: String, timeout: Long) {
|
||||
Timber.v("ImapBackendPusher for folder %s sleeping for %d ms", folderServerId, timeout)
|
||||
pushFolderSleeping[folderServerId] = idleRefreshManager.startTimer(timeout, ::refresh)
|
||||
}
|
||||
|
||||
private fun cancelRetryTimer(folderServerId: String) {
|
||||
Timber.v("Canceling ImapBackendPusher retry timer for folder %s", folderServerId)
|
||||
pushFolderSleeping.remove(folderServerId)?.cancel()
|
||||
}
|
||||
|
||||
private fun isWaitingForRetry(folderServerId: String): Boolean {
|
||||
return pushFolderSleeping[folderServerId]?.isWaiting == true
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
Timber.v("Refreshing ImapBackendPusher (at least one retry timer has expired)")
|
||||
|
||||
val currentFolderServerIds = synchronized(lock) { currentFolderServerIds }
|
||||
updateFolders(currentFolderServerIds)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue