Added EventManagerConfig parameter to all EventListener functions.

This commit is contained in:
Neil Marietta 2021-11-17 14:59:16 +01:00
parent 52ff3a3e33
commit 9cf66be008
21 changed files with 334 additions and 131 deletions

View File

@ -1,3 +1,23 @@
## EventManager Version [1.19.1]
Nov 18, 2021
### Dependencies
- Contact 1.19.1.
- EventManager 1.19.1.
- Mail Settings 1.19.1.
- User 1.19.1.
- User Settings 1.19.1.
### New Features
- Added EventManagerConfig parameter to all EventListener functions.
### Breaking Changes
- EventListener functions signature changed (userId removed, added config).
## Plan Presentation [1.18.1]
17 Nov, 2021

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 18, 0)
libVersion = Version(1, 19, 1)
android()

View File

@ -26,8 +26,8 @@ import me.proton.core.contact.domain.entity.ContactEmailId
import me.proton.core.contact.domain.entity.ContactId
import me.proton.core.contact.domain.repository.ContactLocalDataSource
import me.proton.core.contact.domain.repository.ContactRepository
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -62,7 +62,10 @@ class ContactEmailEventListener @Inject constructor(
override val type = Type.Core
override val order = 2
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactEmailResource>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, ContactEmailResource>>? {
return response.body.deserializeOrNull<ContactEmailsEvents>()?.contactEmails?.map {
Event(requireNotNull(Action.map[it.action]), it.id, it.contactEmail)
}
@ -72,31 +75,31 @@ class ContactEmailEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onPrepare(userId: UserId, entities: List<ContactEmailResource>) {
override suspend fun onPrepare(config: EventManagerConfig, entities: List<ContactEmailResource>) {
// Don't fetch Contacts that will be created in this set of modifications.
val contactActions = contactEventListener.getActionMap(userId)
val contactActions = contactEventListener.getActionMap(config)
val createContactIds = contactActions[Action.Create].orEmpty().map { it.key }.toHashSet()
// Make sure we'll fetch other Contacts.
entities.filterNot { createContactIds.contains(it.contactId) }.forEach {
contactRepository.getContactWithCards(userId, ContactId(it.contactId), refresh = false)
contactRepository.getContactWithCards(config.userId, ContactId(it.contactId), refresh = false)
}
}
override suspend fun onCreate(userId: UserId, entities: List<ContactEmailResource>) {
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) }
override suspend fun onCreate(config: EventManagerConfig, entities: List<ContactEmailResource>) {
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(config.userId)) }
}
override suspend fun onUpdate(userId: UserId, entities: List<ContactEmailResource>) {
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(userId)) }
override suspend fun onUpdate(config: EventManagerConfig, entities: List<ContactEmailResource>) {
entities.forEach { contactLocalDataSource.upsertContactEmails(it.toContactEmail(config.userId)) }
}
override suspend fun onDelete(userId: UserId, keys: List<String>) {
override suspend fun onDelete(config: EventManagerConfig, keys: List<String>) {
contactLocalDataSource.deleteContactEmails(*keys.map { ContactEmailId(it) }.toTypedArray())
}
override suspend fun onResetAll(userId: UserId) {
override suspend fun onResetAll(config: EventManagerConfig) {
// Already handled in ContactEventListener:
// contactLocalDataSource.deleteAllContactEmails(userId)
// contactLocalDataSource.deleteAllContactEmails(config.userId)
// contactRepository.getAllContactEmails(userId, refresh = true)
}
}

View File

@ -25,8 +25,8 @@ import me.proton.core.contact.data.local.db.ContactDatabase
import me.proton.core.contact.domain.entity.ContactId
import me.proton.core.contact.domain.repository.ContactLocalDataSource
import me.proton.core.contact.domain.repository.ContactRepository
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -60,7 +60,10 @@ class ContactEventListener @Inject constructor(
override val type = Type.Core
override val order = 1
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactWithCardsResource>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, ContactWithCardsResource>>? {
return response.body.deserializeOrNull<ContactsEvents>()?.contacts?.map {
Event(requireNotNull(Action.map[it.action]), it.id, it.contact)
}
@ -70,20 +73,20 @@ class ContactEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onCreate(userId: UserId, entities: List<ContactWithCardsResource>) {
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) }
override suspend fun onCreate(config: EventManagerConfig, entities: List<ContactWithCardsResource>) {
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(config.userId)) }
}
override suspend fun onUpdate(userId: UserId, entities: List<ContactWithCardsResource>) {
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(userId)) }
override suspend fun onUpdate(config: EventManagerConfig, entities: List<ContactWithCardsResource>) {
entities.forEach { contactLocalDataSource.upsertContactWithCards(it.toContactWithCards(config.userId)) }
}
override suspend fun onDelete(userId: UserId, keys: List<String>) {
override suspend fun onDelete(config: EventManagerConfig, keys: List<String>) {
contactLocalDataSource.deleteContacts(*keys.map { ContactId(it) }.toTypedArray())
}
override suspend fun onResetAll(userId: UserId) {
contactLocalDataSource.deleteAllContacts(userId)
contactRepository.getAllContacts(userId, refresh = true)
override suspend fun onResetAll(config: EventManagerConfig) {
contactLocalDataSource.deleteAllContacts(config.userId)
contactRepository.getAllContacts(config.userId, refresh = true)
}
}

View File

@ -24,7 +24,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 18, 1)
libVersion = Version(1, 19, 1)
android()

View File

@ -18,7 +18,6 @@
package me.proton.core.eventmanager.data
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.data.api.response.GetCalendarEventsResponse
import me.proton.core.eventmanager.data.api.response.GetCalendarLatestEventIdResponse
import me.proton.core.eventmanager.data.api.response.GetCoreEventsResponse
@ -37,7 +36,7 @@ interface EventDeserializer {
val config: EventManagerConfig
val endpoint: String
fun deserializeLatestEventId(response: EventIdResponse): EventId
fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata
fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata
}
internal data class CoreEventDeserializer(
@ -49,10 +48,10 @@ internal data class CoreEventDeserializer(
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
EventId(response.body.deserialize<GetCoreLatestEventIdResponse>().eventId)
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata =
response.body.deserialize<GetCoreEventsResponse>().let {
EventMetadata(
userId = userId,
userId = config.userId,
eventId = eventId,
config = config,
nextEventId = EventId(it.eventId),
@ -73,10 +72,10 @@ internal data class CalendarEventDeserializer(
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
EventId(response.body.deserialize<GetCalendarLatestEventIdResponse>().eventId)
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata =
response.body.deserialize<GetCalendarEventsResponse>().let {
EventMetadata(
userId = userId,
userId = config.userId,
eventId = eventId,
config = config,
nextEventId = EventId(it.eventId),
@ -97,10 +96,10 @@ internal class DriveEventDeserializer(
override fun deserializeLatestEventId(response: EventIdResponse): EventId =
EventId(response.body.deserialize<GetDriveLatestEventIdResponse>().eventId)
override fun deserializeEventMetadata(userId: UserId, eventId: EventId, response: EventsResponse): EventMetadata =
override fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata =
response.body.deserialize<GetDriveEventsResponse>().let {
EventMetadata(
userId = userId,
userId = config.userId,
eventId = eventId,
config = config,
nextEventId = EventId(it.eventId),

View File

@ -78,7 +78,7 @@ class EventManagerImpl @AssistedInject constructor(
response: EventsResponse
): Map<EventListener<*, *>, List<Event<*, *>>> {
return eventListenersByOrder.values.flatten().associateWith { eventListener ->
eventListener.deserializeEvents(response).orEmpty()
eventListener.deserializeEvents(config, response).orEmpty()
}
}
@ -160,7 +160,7 @@ class EventManagerImpl @AssistedInject constructor(
) {
// Fully sequential and ordered.
eventListenersByOrder.values.flatten().forEach {
it.notifyResetAll(metadata.userId)
it.notifyResetAll(config)
}
}.onFailure {
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
@ -181,11 +181,11 @@ class EventManagerImpl @AssistedInject constructor(
// Set actions for all listeners.
val eventsByListener = deserializeEventsByListener(requireNotNull(metadata.response))
eventsByListener.forEach { (eventListener, list) ->
eventListener.setActionMap(metadata.userId, list as List<Nothing>)
eventListener.setActionMap(config, list as List<Nothing>)
}
// Notify prepare for all listeners.
eventListenersByOrder.values.flatten().forEach { eventListener ->
eventListener.notifyPrepare(metadata.userId)
eventListener.notifyPrepare(config)
}
}.onFailure {
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
@ -205,7 +205,7 @@ class EventManagerImpl @AssistedInject constructor(
) {
// Fully sequential and ordered.
eventListenersByOrder.values.flatten().forEach { eventListener ->
eventListener.notifyEvents(metadata.userId)
eventListener.notifyEvents(config)
}
}.onFailure {
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
@ -225,7 +225,7 @@ class EventManagerImpl @AssistedInject constructor(
) {
// Fully sequential and ordered.
eventListenersByOrder.values.flatten().forEach { eventListener ->
eventListener.notifyComplete(metadata.userId)
eventListener.notifyComplete(config)
}
}.onFailure {
CoreLogger.e(LogTag.NOTIFY_ERROR, it)
@ -336,7 +336,7 @@ class EventManagerImpl @AssistedInject constructor(
eventMetadataRepository.getEvents(config.userId, eventId, deserializer.endpoint)
override suspend fun deserializeEventMetadata(eventId: EventId, response: EventsResponse): EventMetadata =
deserializer.deserializeEventMetadata(config.userId, eventId, response)
deserializer.deserializeEventMetadata(eventId, response)
companion object {
// Constraint: retriesBeforeNotifyResetAll < retriesBeforeReset.

View File

@ -33,6 +33,7 @@ import me.proton.core.account.domain.entity.AccountDetails
import me.proton.core.account.domain.entity.AccountState
import me.proton.core.accountmanager.domain.AccountManager
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.data.listener.CalendarEventListener
import me.proton.core.eventmanager.data.listener.ContactEventListener
import me.proton.core.eventmanager.data.listener.UserEventListener
import me.proton.core.eventmanager.domain.EventListener
@ -44,11 +45,15 @@ import me.proton.core.eventmanager.domain.entity.EventIdResponse
import me.proton.core.eventmanager.domain.entity.EventMetadata
import me.proton.core.eventmanager.domain.entity.EventsResponse
import me.proton.core.eventmanager.domain.entity.State
import me.proton.core.eventmanager.domain.extension.asCalendar
import me.proton.core.eventmanager.domain.extension.asDrive
import me.proton.core.eventmanager.domain.repository.EventMetadataRepository
import me.proton.core.eventmanager.domain.work.EventWorkerManager
import me.proton.core.presentation.app.AppLifecycleProvider
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class EventManagerImplTest {
@ -64,6 +69,7 @@ class EventManagerImplTest {
private lateinit var userEventListener: UserEventListener
private lateinit var contactEventListener: ContactEventListener
private lateinit var calendarEventListener: CalendarEventListener
private lateinit var listeners: Set<EventListener<*, *>>
private val user1 = Account(
@ -89,17 +95,22 @@ class EventManagerImplTest {
private val user1Config = EventManagerConfig.Core(user1.userId)
private val user2Config = EventManagerConfig.Core(user2.userId)
private val calendarId = "calendarId"
private val calendarConfig = EventManagerConfig.Calendar(user1.userId, calendarId)
private val eventId = "eventId"
private val appState = MutableStateFlow(AppLifecycleProvider.State.Foreground)
private lateinit var user1Manager: EventManager
private lateinit var user2Manager: EventManager
private lateinit var calendarManager: EventManager
@Before
fun before() {
userEventListener = spyk(UserEventListener())
contactEventListener = spyk(ContactEventListener())
listeners = setOf<EventListener<*, *>>(userEventListener, contactEventListener)
calendarEventListener = spyk(CalendarEventListener())
listeners = setOf(userEventListener, contactEventListener, calendarEventListener)
appLifecycleProvider = mockk {
every { state } returns appState
@ -129,6 +140,7 @@ class EventManagerImplTest {
eventManagerProvider = EventManagerProviderImpl(eventManagerFactor, listeners)
user1Manager = eventManagerProvider.get(user1Config)
user2Manager = eventManagerProvider.get(user2Config)
calendarManager = eventManagerProvider.get(calendarConfig)
coEvery { eventMetadataRepository.getLatestEventId(any(), any()) } returns
EventIdResponse("{ \"EventID\": \"$eventId\" }")
@ -145,6 +157,9 @@ class EventManagerImplTest {
coEvery { eventMetadataRepository.get(user2Config) } returns
listOf(EventMetadata(user2.userId, EventId(eventId), user2Config, createdAt = 1))
coEvery { eventMetadataRepository.get(calendarConfig) } returns
listOf(EventMetadata(user1.userId, EventId(eventId), calendarConfig, createdAt = 1))
}
@Test
@ -156,34 +171,34 @@ class EventManagerImplTest {
coVerify(exactly = 2) { userEventListener.inTransaction(any()) }
coVerify(exactly = 2) { contactEventListener.inTransaction(any()) }
coVerify(exactly = 1) { userEventListener.onPrepare(user1.userId, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user1.userId, any()) }
coVerify(exactly = 1) { userEventListener.onPrepare(user1Config, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user1Config, any()) }
coVerify(exactly = 1) { contactEventListener.onPrepare(user1.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onUpdate(user1.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onDelete(user1.userId, any()) }
coVerify(exactly = 1) { contactEventListener.onCreate(user1.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onPartial(user1.userId, any()) }
coVerify(exactly = 1) { contactEventListener.onPrepare(user1Config, any()) }
coVerify(exactly = 0) { contactEventListener.onUpdate(user1Config, any()) }
coVerify(exactly = 0) { contactEventListener.onDelete(user1Config, any()) }
coVerify(exactly = 1) { contactEventListener.onCreate(user1Config, any()) }
coVerify(exactly = 0) { contactEventListener.onPartial(user1Config, any()) }
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyPrepare) }
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyEvents) }
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user1Config, any(), State.NotifyComplete) }
coVerify(exactly = 1) { eventMetadataRepository.updateState(user1Config, any(), State.Completed) }
coVerify(exactly = 1) { userEventListener.onPrepare(user2.userId, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user2.userId, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user2.userId, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user2.userId, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user2.userId, any()) }
coVerify(exactly = 1) { userEventListener.onPrepare(user2Config, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user2Config, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user2Config, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user2Config, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user2Config, any()) }
coVerify(exactly = 1) { contactEventListener.onPrepare(user2.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onUpdate(user2.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onDelete(user2.userId, any()) }
coVerify(exactly = 1) { contactEventListener.onCreate(user2.userId, any()) }
coVerify(exactly = 0) { contactEventListener.onPartial(user2.userId, any()) }
coVerify(exactly = 1) { contactEventListener.onPrepare(user2Config, any()) }
coVerify(exactly = 0) { contactEventListener.onUpdate(user2Config, any()) }
coVerify(exactly = 0) { contactEventListener.onDelete(user2Config, any()) }
coVerify(exactly = 1) { contactEventListener.onCreate(user2Config, any()) }
coVerify(exactly = 0) { contactEventListener.onPartial(user2Config, any()) }
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyPrepare) }
coVerify(atLeast = 1) { eventMetadataRepository.updateState(user2Config, any(), State.NotifyEvents) }
@ -194,25 +209,25 @@ class EventManagerImplTest {
@Test
fun callOnPrepareThrowException() = runBlocking {
// GIVEN
coEvery { userEventListener.onPrepare(user1.userId, any()) } throws Exception("IOException")
coEvery { userEventListener.onPrepare(user1Config, any()) } throws Exception("IOException")
// WHEN
user1Manager.process()
// THEN
coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyPrepare) }
coVerify(exactly = 0) { userEventListener.onUpdate(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onUpdate(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.inTransaction(any()) }
}
@Test
fun callOnUpdateThrowException() = runBlocking {
// GIVEN
coEvery { userEventListener.onUpdate(user1.userId, any()) } throws Exception("SqlForeignKeyException")
coEvery { userEventListener.onUpdate(user1Config, any()) } throws Exception("SqlForeignKeyException")
// WHEN
user1Manager.process()
// THEN
coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyPrepare) }
coVerify(exactly = 2) { eventMetadataRepository.updateState(any(), any(), State.NotifyEvents) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) }
coVerify(exactly = 1) { userEventListener.inTransaction(any()) }
}
@ -225,11 +240,11 @@ class EventManagerImplTest {
// WHEN
user1Manager.process()
// THEN
coVerify(exactly = 1) { userEventListener.onPrepare(user1.userId, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user1.userId, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user1.userId, any()) }
coVerify(exactly = 1) { userEventListener.onPrepare(user1Config, any()) }
coVerify(exactly = 1) { userEventListener.onUpdate(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onDelete(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onCreate(user1Config, any()) }
coVerify(exactly = 0) { userEventListener.onPartial(user1Config, any()) }
}
@Test
@ -245,4 +260,23 @@ class EventManagerImplTest {
coVerify(exactly = 0) { userEventListener.onCreate(any(), any()) }
coVerify(exactly = 0) { userEventListener.onPartial(any(), any()) }
}
@Test
fun tryCastEventManagerConfig() = runBlocking {
// GIVEN
coEvery { eventMetadataRepository.getEvents(any(), any(), any()) } returns
EventsResponse(TestEvents.calendarFullEventsResponse)
// WHEN
calendarManager.process()
// THEN
coVerify(exactly = 1) { calendarEventListener.onPrepare(any(), any()) }
coVerify(exactly = 0) { calendarEventListener.onUpdate(any(), any()) }
coVerify(exactly = 0) { calendarEventListener.onDelete(any(), any()) }
coVerify(exactly = 1) { calendarEventListener.onCreate(any(), any()) }
coVerify(exactly = 0) { calendarEventListener.onPartial(any(), any()) }
assertFailsWith(ClassCastException::class) {
calendarEventListener.config.asDrive()
}
assertEquals(calendarId, calendarEventListener.config.asCalendar().calendarId)
}
}

View File

@ -244,4 +244,24 @@ object TestEvents {
]
}
""".trimIndent()
val calendarFullEventsResponse =
"""
{
"Code": 1000,
"CalendarModelEventID": "ACXDmTaBub14w==",
"Refresh": 0,
"More": 0,
"Calendars": [
{
"ID": "afeaefaeTaBub14w==",
"Action": 1,
"Calendar": {
"ID": "a29olIjFv0rnXxBhSMw=="
}
}
]
}
""".trimIndent()
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.eventmanager.data.listener
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
import me.proton.core.util.kotlin.deserializeOrNull
@Serializable
data class CalendarsEvents(
@SerialName("Calendars")
val calendars: List<CalendarEvent>
)
@Serializable
data class CalendarEvent(
@SerialName("ID")
val id: String,
@SerialName("Action")
val action: Int,
@SerialName("Calendar")
val calendar: CalendarResource? = null
)
@Serializable
data class CalendarResource(
@SerialName("ID")
val id: String
)
class CalendarEventListener : EventListener<String, CalendarResource>() {
override val type = Type.Calendar
override val order = 1
lateinit var config: EventManagerConfig
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, CalendarResource>>? {
val events = response.body.deserializeOrNull<CalendarsEvents>()
return events?.calendars?.map {
Event(requireNotNull(Action.map[it.action]), it.id, it.calendar)
}
}
override suspend fun <R> inTransaction(block: suspend () -> R): R {
// Db.inTransaction(block)
return block()
}
override suspend fun onPrepare(config: EventManagerConfig, entities: List<CalendarResource>) {
super.onPrepare(config, entities)
}
override suspend fun onCreate(config: EventManagerConfig, entities: List<CalendarResource>) {
this.config = config
}
}

View File

@ -22,6 +22,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.contact.data.api.resource.ContactWithCardsResource
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -48,7 +49,10 @@ class ContactEventListener : EventListener<String, ContactWithCardsResource>() {
override val type = Type.Core
override val order = 1
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, ContactWithCardsResource>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, ContactWithCardsResource>>? {
return response.body.deserializeOrNull<ContactsEvents>()?.contacts?.map {
Event(requireNotNull(Action.map[it.action]), it.id, it.contact)
}

View File

@ -21,6 +21,7 @@ package me.proton.core.eventmanager.data.listener
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -38,7 +39,10 @@ class UserEventListener : EventListener<String, UserResponse>() {
override val type = Type.Core
override val order = 0
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserResponse>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, UserResponse>>? {
return response.body.deserializeOrNull<UserEvents>()?.let {
listOf(Event(Action.Update, it.user.id, it.user))
}

View File

@ -18,16 +18,15 @@
package me.proton.core.eventmanager.domain
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
import me.proton.core.eventmanager.domain.extension.groupByAction
import me.proton.core.util.kotlin.takeIfNotEmpty
abstract class EventListener<K, T : Any> : TransactionHandler {
abstract class EventListener<Key : Any, Type : Any> : TransactionHandler {
private val actionMapByUserId = mutableMapOf<UserId, Map<Action, List<Event<K, T>>>>()
private val actionMapByConfig = mutableMapOf<EventManagerConfig, Map<Action, List<Event<Key, Type>>>>()
/**
* Type of Event loop.
@ -58,7 +57,7 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
/**
* Listener [type] to associate with this [EventListener].
*/
abstract val type: Type
abstract val type: EventListener.Type
/**
* The degrees of separation from this entity to the User entity.
@ -76,8 +75,8 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: The map is created just before [onPrepare], and cleared after [onComplete].
*/
fun getActionMap(userId: UserId): Map<Action, List<Event<K, T>>> {
return actionMapByUserId.getOrPut(userId) { emptyMap() }
fun getActionMap(config: EventManagerConfig): Map<Action, List<Event<Key, Type>>> {
return actionMapByConfig.getOrPut(config) { emptyMap() }
}
/**
@ -85,8 +84,8 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: Called before [notifyPrepare] and after [notifyComplete].
*/
fun setActionMap(userId: UserId, events: List<Event<K, T>>) {
actionMapByUserId[userId] = events.groupByAction()
fun setActionMap(config: EventManagerConfig, events: List<Event<Key, Type>>) {
actionMapByConfig[config] = events.groupByAction()
}
/**
@ -94,10 +93,10 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: No transaction wraps this function.
*/
suspend fun notifyPrepare(userId: UserId) {
val actions = getActionMap(userId)
suspend fun notifyPrepare(config: EventManagerConfig) {
val actions = getActionMap(config)
val entities = actions[Action.Create].orEmpty() + actions[Action.Update].orEmpty()
entities.takeIfNotEmpty()?.let { list -> onPrepare(userId, list.mapNotNull { it.entity }) }
entities.takeIfNotEmpty()?.let { list -> onPrepare(config, list.mapNotNull { it.entity }) }
}
/**
@ -105,12 +104,12 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: A transaction wraps this function.
*/
suspend fun notifyEvents(userId: UserId) {
val actions = getActionMap(userId)
actions[Action.Create]?.takeIfNotEmpty()?.let { list -> onCreate(userId, list.mapNotNull { it.entity }) }
actions[Action.Update]?.takeIfNotEmpty()?.let { list -> onUpdate(userId, list.mapNotNull { it.entity }) }
actions[Action.Partial]?.takeIfNotEmpty()?.let { list -> onPartial(userId, list.mapNotNull { it.entity }) }
actions[Action.Delete]?.takeIfNotEmpty()?.let { list -> onDelete(userId, list.map { it.key }) }
suspend fun notifyEvents(config: EventManagerConfig) {
val actions = getActionMap(config)
actions[Action.Create]?.takeIfNotEmpty()?.let { list -> onCreate(config, list.mapNotNull { it.entity }) }
actions[Action.Update]?.takeIfNotEmpty()?.let { list -> onUpdate(config, list.mapNotNull { it.entity }) }
actions[Action.Partial]?.takeIfNotEmpty()?.let { list -> onPartial(config, list.mapNotNull { it.entity }) }
actions[Action.Delete]?.takeIfNotEmpty()?.let { list -> onDelete(config, list.map { it.key }) }
}
/**
@ -118,8 +117,8 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: No transaction wraps this function.
*/
suspend fun notifyResetAll(userId: UserId) {
onResetAll(userId)
suspend fun notifyResetAll(config: EventManagerConfig) {
onResetAll(config)
}
/**
@ -127,15 +126,15 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* Note: No transaction wraps this function.
*/
suspend fun notifyComplete(userId: UserId) {
onComplete(userId)
setActionMap(userId, emptyList())
suspend fun notifyComplete(config: EventManagerConfig) {
onComplete(config)
setActionMap(config, emptyList())
}
/**
* Deserialize [response] into a typed list of [Event].
*/
abstract suspend fun deserializeEvents(response: EventsResponse): List<Event<K, T>>?
abstract suspend fun deserializeEvents(config: EventManagerConfig, response: EventsResponse): List<Event<Key, Type>>?
/**
* Called before applying a set of modifications to prepare any additional action (e.g. fetch foreign entities).
@ -144,14 +143,14 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* @see onComplete
*/
open suspend fun onPrepare(userId: UserId, entities: List<T>) = Unit
open suspend fun onPrepare(config: EventManagerConfig, entities: List<Type>) = Unit
/**
* Called at the end of a set of modifications, whether it is successful or not.
*
* @see onPrepare
*/
open suspend fun onComplete(userId: UserId) = Unit
open suspend fun onComplete(config: EventManagerConfig) = Unit
/**
* Called to created entities in persistence.
@ -162,7 +161,7 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* @see onPrepare
*/
open suspend fun onCreate(userId: UserId, entities: List<T>) = Unit
open suspend fun onCreate(config: EventManagerConfig, entities: List<Type>) = Unit
/**
* Called to update or insert entities in persistence.
@ -173,7 +172,7 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* @see onPrepare
*/
open suspend fun onUpdate(userId: UserId, entities: List<T>) = Unit
open suspend fun onUpdate(config: EventManagerConfig, entities: List<Type>) = Unit
/**
* Called to partially update entities in persistence.
@ -184,19 +183,19 @@ abstract class EventListener<K, T : Any> : TransactionHandler {
*
* @see onPrepare
*/
open suspend fun onPartial(userId: UserId, entities: List<T>) = Unit
open suspend fun onPartial(config: EventManagerConfig, entities: List<Type>) = Unit
/**
* Called to delete, if exist, entities in persistence.
*
* Note: A transaction wraps this function and must return as fast as possible.
*/
open suspend fun onDelete(userId: UserId, keys: List<K>) = Unit
open suspend fun onDelete(config: EventManagerConfig, keys: List<Key>) = Unit
/**
* Called to reset/delete all entities in persistence, for this type.
*
* Note: Usually a good time the fetch minimal data after deletion.
*/
open suspend fun onResetAll(userId: UserId) = Unit
open suspend fun onResetAll(config: EventManagerConfig) = Unit
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.eventmanager.domain.extension
import me.proton.core.eventmanager.domain.EventManagerConfig
fun EventManagerConfig.asCalendar(): EventManagerConfig.Calendar = this as EventManagerConfig.Calendar
fun EventManagerConfig.asDrive(): EventManagerConfig.Drive = this as EventManagerConfig.Drive

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 18, 0)
libVersion = Version(1, 19, 1)
android()

View File

@ -20,8 +20,8 @@ package me.proton.core.mailsettings.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -48,7 +48,10 @@ class MailSettingsEventListener @Inject constructor(
override val type = Type.Core
override val order = 1
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, MailSettingsResponse>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, MailSettingsResponse>>? {
return response.body.deserializeOrNull<MailSettingsEvents>()?.let {
listOf(Event(Action.Update, "null", it.settings))
}
@ -58,11 +61,11 @@ class MailSettingsEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onUpdate(userId: UserId, entities: List<MailSettingsResponse>) {
repository.updateMailSettings(entities.first().toMailSettings(userId))
override suspend fun onUpdate(config: EventManagerConfig, entities: List<MailSettingsResponse>) {
repository.updateMailSettings(entities.first().toMailSettings(config.userId))
}
override suspend fun onResetAll(userId: UserId) {
repository.getMailSettings(userId, refresh = true)
override suspend fun onResetAll(config: EventManagerConfig) {
repository.getMailSettings(config.userId, refresh = true)
}
}

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 18, 2)
libVersion = Version(1, 19, 1)
android()

View File

@ -20,8 +20,8 @@ package me.proton.core.usersettings.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -48,7 +48,10 @@ class UserSettingsEventListener @Inject constructor(
override val type = Type.Core
override val order = 1
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserSettingsResponse>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, UserSettingsResponse>>? {
return response.body.deserializeOrNull<UserSettingsEvents>()?.let {
listOf(Event(Action.Update, "null", it.settings))
}
@ -58,11 +61,11 @@ class UserSettingsEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onUpdate(userId: UserId, entities: List<UserSettingsResponse>) {
repository.updateUserSettings(entities.first().toUserSettings(userId))
override suspend fun onUpdate(config: EventManagerConfig, entities: List<UserSettingsResponse>) {
repository.updateUserSettings(entities.first().toUserSettings(config.userId))
}
override suspend fun onResetAll(userId: UserId) {
repository.getUserSettings(userId, refresh = true)
override suspend fun onResetAll(config: EventManagerConfig) {
repository.getUserSettings(config.userId, refresh = true)
}
}

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 18, 0)
libVersion = Version(1, 19, 1)
android()

View File

@ -20,8 +20,8 @@ package me.proton.core.user.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -59,7 +59,10 @@ class UserAddressEventListener @Inject constructor(
override val type = Type.Core
override val order = 1
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, AddressResponse>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, AddressResponse>>? {
return response.body.deserializeOrNull<UserAddressEvents>()?.addresses?.map {
Event(requireNotNull(Action.map[it.action]), it.address.id, it.address)
}
@ -69,20 +72,20 @@ class UserAddressEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onCreate(userId: UserId, entities: List<AddressResponse>) {
userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) })
override suspend fun onCreate(config: EventManagerConfig, entities: List<AddressResponse>) {
userAddressRepository.updateAddresses(entities.map { it.toAddress(config.userId) })
}
override suspend fun onUpdate(userId: UserId, entities: List<AddressResponse>) {
userAddressRepository.updateAddresses(entities.map { it.toAddress(userId) })
override suspend fun onUpdate(config: EventManagerConfig, entities: List<AddressResponse>) {
userAddressRepository.updateAddresses(entities.map { it.toAddress(config.userId) })
}
override suspend fun onDelete(userId: UserId, keys: List<String>) {
override suspend fun onDelete(config: EventManagerConfig, keys: List<String>) {
userAddressRepository.deleteAddresses(keys.map { AddressId(it) })
}
override suspend fun onResetAll(userId: UserId) {
userAddressRepository.deleteAllAddresses(userId)
userAddressRepository.getAddresses(userId, refresh = true)
override suspend fun onResetAll(config: EventManagerConfig) {
userAddressRepository.deleteAllAddresses(config.userId)
userAddressRepository.getAddresses(config.userId, refresh = true)
}
}

View File

@ -20,8 +20,8 @@ package me.proton.core.user.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventListener
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.eventmanager.domain.entity.Action
import me.proton.core.eventmanager.domain.entity.Event
import me.proton.core.eventmanager.domain.entity.EventsResponse
@ -48,7 +48,10 @@ class UserEventListener @Inject constructor(
override val type = Type.Core
override val order = 0
override suspend fun deserializeEvents(response: EventsResponse): List<Event<String, UserResponse>>? {
override suspend fun deserializeEvents(
config: EventManagerConfig,
response: EventsResponse
): List<Event<String, UserResponse>>? {
return response.body.deserializeOrNull<UserEvents>()?.let {
listOf(Event(Action.Update, it.user.id, it.user))
}
@ -58,11 +61,11 @@ class UserEventListener @Inject constructor(
return db.inTransaction(block)
}
override suspend fun onUpdate(userId: UserId, entities: List<UserResponse>) {
override suspend fun onUpdate(config: EventManagerConfig, entities: List<UserResponse>) {
userRepository.updateUser(entities.first().toUser())
}
override suspend fun onResetAll(userId: UserId) {
userRepository.getUser(userId, refresh = true)
override suspend fun onResetAll(config: EventManagerConfig) {
userRepository.getUser(config.userId, refresh = true)
}
}